feat(clone): background clone jobs that survive a reload and a server restart
Both clone POSTs take background: true: every check answers synchronously,
then 202 {job}. git runs in a detached runner (scripts/clone-runner.js, its
own process group, stderr to a file — no SIGPIPE when the server restarts);
the server tails it for progress and runs the after-step (workspace / extra
folder) once when it exits. Jobs persist in <data dir>/runtime/clone-jobs.json
(0600, atomic, throttled progress writes); on start a live runner is adopted
(counted against the clone limit), an ended one finished, a queued one
requeued (interrupted if it carried a typed login), the rest interrupted.
GET /clone/jobs, DELETE /clone/jobs/:id, POST /clone/jobs/:id/cancel (queued:
dequeued; running: SIGTERM to the runner's group -> cancelled, no after-step)
and /retry (new job from the stored request, never a credential). The
held-open path is unchanged; worktree.gitCommand builds the runner's argv
and git-identity.cloneFailure the shared error classification.
This commit is contained in:
+151
-2
@@ -3,7 +3,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Agent Dashboard for Claude Code API
|
||||
version: 2.8.40
|
||||
version: 2.8.50
|
||||
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
|
||||
contact:
|
||||
name: Nguyễn Ngọc Trí Vĩ
|
||||
@@ -17346,9 +17346,100 @@ paths:
|
||||
type: string
|
||||
pattern: ^[A-Za-z0-9_-]{16,64}$
|
||||
description: The caller's name for this clone, to poll `/api/lanes/clone/progress/{token}`.
|
||||
background:
|
||||
type: boolean
|
||||
description: 'Run every check now, then clone as a background job that outlives the request and a server restart (a detached runner): 202 `{job}`. 400 EBADFIELD when not a boolean.'
|
||||
responses:
|
||||
'201':
|
||||
description: '{lane, created: true, path}'
|
||||
'202':
|
||||
description: '`background: true`: {job} — a CloneJob (see `/api/lanes/clone/jobs`).'
|
||||
/api/lanes/clone/jobs:
|
||||
get:
|
||||
tags:
|
||||
- Git identity
|
||||
summary: The caller's background clone jobs, newest first
|
||||
description: 'CloneJob: {id, kind: workspace|source, state: queued|running|done|failed|interrupted|cancelled, url, name, parent, target, laneId, position (1-based while queued), phase, label, percent, detail, error: {code, message, auth?} | null, result: {laneId, path, created?} | null, createdAt, finishedAt, request: {url, parent, name, tool?, title?, partial?}, pid}. `request` never holds a credential. Kept in `<data dir>/runtime/clone-jobs.json`; a job whose runner still runs after a restart is adopted, a dead one becomes `interrupted` (EINTERRUPTED). Done jobs are dropped after 24 h, the rest after 7 days. Only the caller''s own (local mode: all).'
|
||||
operationId: listCloneJobs
|
||||
responses:
|
||||
'200':
|
||||
description: '{jobs: CloneJob[]}'
|
||||
/api/lanes/clone/jobs/{id}:
|
||||
delete:
|
||||
tags:
|
||||
- Git identity
|
||||
summary: Dismiss a finished background clone job
|
||||
operationId: dismissCloneJob
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: '{ok: true}'
|
||||
'404':
|
||||
description: ENOTFOUND — not the caller's job
|
||||
'409':
|
||||
description: EBUSY — still queued or running
|
||||
/api/lanes/clone/jobs/{id}/cancel:
|
||||
post:
|
||||
tags:
|
||||
- Git identity
|
||||
summary: Cancel a queued or running background clone
|
||||
description: 'Queued: taken out of the queue, never started. Running: the runner''s process group is SIGTERMed (git removes its half-made folder). The job becomes `cancelled` with ECANCELLED and never makes its workspace / extra folder. Same-origin.'
|
||||
operationId: cancelCloneJob
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: '{job}'
|
||||
'404':
|
||||
description: ENOTFOUND — not the caller's job
|
||||
'409':
|
||||
description: EDONE — it already finished
|
||||
/api/lanes/clone/jobs/{id}/retry:
|
||||
post:
|
||||
tags:
|
||||
- Git identity
|
||||
summary: Retry a failed, interrupted or cancelled background clone
|
||||
description: A NEW job from the old one's `request` (and kind / workspace), through every check of a fresh clone POST; the old job is dropped once the new one exists. `credentials` as on the clone POST, for this run only. Same-origin.
|
||||
operationId: retryCloneJob
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: []
|
||||
properties:
|
||||
credentials:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
remember:
|
||||
type: boolean
|
||||
responses:
|
||||
'202':
|
||||
description: '{job} — the new CloneJob'
|
||||
'404':
|
||||
description: 'ENOTFOUND — not the caller''s job (ENOLANE: its workspace is gone)'
|
||||
'409':
|
||||
description: EBUSY — queued, running or done
|
||||
/api/lanes/clone/progress/{token}:
|
||||
get:
|
||||
tags:
|
||||
@@ -17841,7 +17932,7 @@ paths:
|
||||
- Artifacts
|
||||
summary: 'Skills from files: a file, a folder, a .zip/.skill or several skills'
|
||||
operationId: uploadArtifactSkills
|
||||
description: '`paths` is a JSON array of each file''s relative path. Zip sizes are read before inflating; nothing is stored if any skill fails.'
|
||||
description: '`paths` is a JSON array of each file''s relative path. Zip sizes are read before inflating; nothing is stored if any skill fails. A skill whose name matches one the caller already has (not a workspace''s) becomes that row''s new version (`replaced: true`, `changed`) instead of a second row; `id` (Share again) puts the ONE uploaded skill onto that row, under the row''s name.'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -17864,6 +17955,9 @@ paths:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
description: 'Share again: the skill row the upload replaces.'
|
||||
responses:
|
||||
'201':
|
||||
description: Created.
|
||||
@@ -19205,6 +19299,9 @@ paths:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
description: A typed HTTPS login for this one clone.
|
||||
background:
|
||||
type: boolean
|
||||
description: 'Run as a background clone job (see `/api/lanes/clone/jobs`): the checks answer at once, then 202 `{job}` with `kind: "source"`.'
|
||||
responses:
|
||||
'201':
|
||||
description: The context view plus `path` (the clone).
|
||||
@@ -19213,6 +19310,8 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
'202':
|
||||
description: '`background: true`: {job} — a CloneJob.'
|
||||
'400':
|
||||
description: EUNSUPPORTED (tool takes no extra folders), a URL/name refusal, or a source check (the clone stays, `path` names it).
|
||||
'403':
|
||||
@@ -22874,6 +22973,56 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/api/artifacts/{id}/reshare:
|
||||
post:
|
||||
tags:
|
||||
- Artifacts
|
||||
summary: 'Share again: a skill''s newest version from its owner''s folder on this server'
|
||||
operationId: reshareArtifact
|
||||
description: 'The owner''s skill folder (the row''s source folder, else the same-named skill of a plugin the owner installed at user scope) becomes the new snapshot, and the row is shared (server mode); installers are then offered Update. 409 ENOSOURCE when the server has no copy: upload the folder with `id` on /api/artifacts/skill/upload. Same-origin only.'
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Artifact id.
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Taken.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
type: object
|
||||
changed:
|
||||
type: boolean
|
||||
'400':
|
||||
description: EBADKIND.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'403':
|
||||
description: EFORBIDDEN.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'404':
|
||||
description: ENOTFOUND.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'409':
|
||||
description: ENOSOURCE.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/api/artifacts/{id}/claim:
|
||||
post:
|
||||
tags:
|
||||
|
||||
Reference in New Issue
Block a user