llm-cli-gateway 2.14.0-rc.1 → 2.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/retrospective-walk/SKILL.md +294 -0
- package/CHANGELOG.md +52 -0
- package/README.md +19 -7
- package/dist/async-job-manager.d.ts +36 -1
- package/dist/async-job-manager.js +276 -20
- package/dist/config.d.ts +11 -0
- package/dist/config.js +37 -1
- package/dist/index.js +20 -3
- package/dist/job-store.d.ts +75 -5
- package/dist/job-store.js +248 -39
- package/dist/postgres-job-store-worker.js +124 -7
- package/dist/sqlite-driver.d.ts +1 -1
- package/dist/sqlite-driver.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: retrospective-walk
|
|
3
|
+
description: Walk a human or agent through a diff, worktree, commit range, gateway job, or episode reference as a structured retrospective. Use after implement-review-fix or multi-LLM review cycles, when reviewing prior jobs or uncommitted work, or when durable evidence is needed for what changed, why it changed, who/when contributed, and captured human or model comments.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retrospective Walk
|
|
7
|
+
|
|
8
|
+
Guide a change review as a narrative with evidence. Prefer this when the user
|
|
9
|
+
needs the story of a change set, not just hunk-level inspection.
|
|
10
|
+
|
|
11
|
+
## Inputs
|
|
12
|
+
|
|
13
|
+
Accept these fields when the user supplies them, and infer conservative
|
|
14
|
+
defaults when they do not:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"scope": "diff | commit-range | worktree | job-id | episode-ref",
|
|
19
|
+
"target": "diff text, HEAD~5..HEAD, path, job id, or episode reference",
|
|
20
|
+
"mode": "guided | agent-driven | summary-only",
|
|
21
|
+
"capture_comments": true,
|
|
22
|
+
"models_for_why": [],
|
|
23
|
+
"include_prior_evidence": true
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Default `mode` to `guided` for a human in the loop, `agent-driven` for automated
|
|
28
|
+
review, and `summary-only` only when explicitly requested or when interaction is
|
|
29
|
+
not possible. Default `capture_comments` to true.
|
|
30
|
+
|
|
31
|
+
## Scope Resolution
|
|
32
|
+
|
|
33
|
+
Resolve the target into immutable evidence before analysis:
|
|
34
|
+
|
|
35
|
+
- `diff`: use the supplied diff text. If the target is empty, capture
|
|
36
|
+
`git diff --no-ext-diff` and `git diff --cached --no-ext-diff`.
|
|
37
|
+
- `commit-range`: capture `git diff --stat <range>`, `git diff --name-status
|
|
38
|
+
<range>`, and per-commit metadata from `git log --format=fuller <range>`.
|
|
39
|
+
- `worktree`: capture status, dirty file list, staged diff, and unstaged diff.
|
|
40
|
+
Do not stash, reset, or mutate the target worktree. If deeper model/tool
|
|
41
|
+
analysis may write files, create a disposable isolated worktree or analyze a
|
|
42
|
+
read-only evidence packet instead.
|
|
43
|
+
- `job-id`: fetch `llm_job_status` and `llm_job_result`; include `cli`,
|
|
44
|
+
`correlationId`, `sessionId`, timestamps, exit status, and output digests.
|
|
45
|
+
- `episode-ref`: resolve through the caller's available episode/DAG context. If
|
|
46
|
+
no episode tool is available, record the unresolved reference and continue
|
|
47
|
+
from any linked diff, job, receipt, or review artifacts the user supplied.
|
|
48
|
+
|
|
49
|
+
If `include_prior_evidence` is true, collect linked validation runs, receipts,
|
|
50
|
+
review reports, approval records, job ids, and session/cache state that are
|
|
51
|
+
available through gateway tools or local artifacts. Treat summaries as claims;
|
|
52
|
+
prefer raw diffs, job outputs, receipt ids, commands, and file:line evidence.
|
|
53
|
+
|
|
54
|
+
## Change Units
|
|
55
|
+
|
|
56
|
+
Group hunks and files into logical change units before narrating them. Favor
|
|
57
|
+
semantic intent over file boundaries, but keep units small enough to review.
|
|
58
|
+
|
|
59
|
+
Use these grouping signals:
|
|
60
|
+
|
|
61
|
+
- Shared feature, bug, invariant, or user-facing behavior.
|
|
62
|
+
- Production code plus its tests and docs.
|
|
63
|
+
- Config/schema/migration changes that must be deployed together.
|
|
64
|
+
- Review-only or evidence-only changes, such as verification reports.
|
|
65
|
+
|
|
66
|
+
Assign stable ids: `cu_001`, `cu_002`, etc. For each unit, record:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"change_unit_id": "cu_001",
|
|
71
|
+
"title": "Short noun phrase",
|
|
72
|
+
"files": ["src/example.ts"],
|
|
73
|
+
"diff_refs": ["commit abc123", "hunk @@ -42,7 +42,9 @@"],
|
|
74
|
+
"what": "Semantic summary of the delta",
|
|
75
|
+
"why": "Intent, rationale, alternatives, and unresolved assumptions",
|
|
76
|
+
"who_when": [
|
|
77
|
+
{
|
|
78
|
+
"actor_type": "human | model | agent | git-author | unknown",
|
|
79
|
+
"identifier": "name, model key, job id, or commit author",
|
|
80
|
+
"timestamp": "RFC3339 if known",
|
|
81
|
+
"provenance": "commit, job, session, receipt, or user statement"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"evidence": ["file:line", "job id", "validation receipt id", "command digest"],
|
|
85
|
+
"comments": []
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What, Why, Who
|
|
90
|
+
|
|
91
|
+
For each change unit:
|
|
92
|
+
|
|
93
|
+
1. Extract **what** directly from the diff, file reads, tests, and docs.
|
|
94
|
+
2. Derive **why** from commit messages, issue/DAG context, review findings,
|
|
95
|
+
prior receipts, job prompts/results, and nearby code intent.
|
|
96
|
+
3. Attribute **who / when** using git author/committer metadata, gateway job
|
|
97
|
+
metadata, session ids, model keys, review outputs, and explicit user
|
|
98
|
+
statements.
|
|
99
|
+
4. Mark inference boundaries. Use `inferred:` when intent or authorship is
|
|
100
|
+
reasoned from evidence rather than directly stated.
|
|
101
|
+
5. Surface missing evidence as an open question instead of filling the gap with
|
|
102
|
+
confidence.
|
|
103
|
+
|
|
104
|
+
Use `multi-llm-review` or direct gateway model calls only when why-synthesis is
|
|
105
|
+
ambiguous, high stakes, or explicitly requested. Omit `model` unless the user
|
|
106
|
+
requested specific variants; use `approvalStrategy:"mcp_managed"` and async job
|
|
107
|
+
handling from `async-job-orchestration` for longer analysis. Ask models for
|
|
108
|
+
intent/rationale synthesis, not for unverified approval.
|
|
109
|
+
|
|
110
|
+
## Comment Capture
|
|
111
|
+
|
|
112
|
+
When `capture_comments` is true, comments are evidence. Capture them at the
|
|
113
|
+
change-unit level and, when possible, at the file/hunk/line location.
|
|
114
|
+
|
|
115
|
+
In `guided` mode, walk one unit at a time:
|
|
116
|
+
|
|
117
|
+
1. Present the unit's what/why/who and evidence.
|
|
118
|
+
2. Ask for comments, concerns, approvals, suggestions, or intent corrections.
|
|
119
|
+
3. Normalize each answer into the schema below.
|
|
120
|
+
4. Continue only after comments are captured or the user skips the unit.
|
|
121
|
+
|
|
122
|
+
In `agent-driven` mode, create agent/model comments only when they add evidence,
|
|
123
|
+
concern, approval, or intent clarification beyond the narrative. In
|
|
124
|
+
`summary-only` mode, do not stop for comments; include an empty comments array
|
|
125
|
+
unless comments were supplied up front.
|
|
126
|
+
|
|
127
|
+
Comment schema:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"comment_id": "cmt_<ulid>",
|
|
132
|
+
"retrospective_id": "retro_<ulid>",
|
|
133
|
+
"change_unit_id": "cu_001",
|
|
134
|
+
"author": {
|
|
135
|
+
"author_type": "human | model",
|
|
136
|
+
"identifier": "werner or claude-4-opus",
|
|
137
|
+
"model_key": "optional; only for model comments"
|
|
138
|
+
},
|
|
139
|
+
"timestamp": "RFC3339",
|
|
140
|
+
"type": "note | concern | approval | suggestion | intent_clarification",
|
|
141
|
+
"text": "Comment body",
|
|
142
|
+
"location": {
|
|
143
|
+
"file": "optional path",
|
|
144
|
+
"hunk_range": "optional @@ range",
|
|
145
|
+
"semantic_unit": "optional semantic code-search node or future semantic id",
|
|
146
|
+
"line_start": 1,
|
|
147
|
+
"line_end": 2
|
|
148
|
+
},
|
|
149
|
+
"linked_receipts": ["validation receipt ids"],
|
|
150
|
+
"linked_jobs": ["async job ids"],
|
|
151
|
+
"metadata": {
|
|
152
|
+
"namespace.key": "freeform extension values"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Generate ids in a stable, sortable form when the runtime has a ULID helper;
|
|
158
|
+
otherwise use `retro_<timestamp>_<short-hash>` and
|
|
159
|
+
`cmt_<timestamp>_<sequence>`.
|
|
160
|
+
|
|
161
|
+
## Output Contract
|
|
162
|
+
|
|
163
|
+
Emit both a readable retrospective and a machine-readable block. Keep the
|
|
164
|
+
machine block fenced as `json` and make it complete enough to persist.
|
|
165
|
+
|
|
166
|
+
Markdown structure:
|
|
167
|
+
|
|
168
|
+
```markdown
|
|
169
|
+
# Retrospective Walk: <target>
|
|
170
|
+
|
|
171
|
+
Retrospective ID: retro_<id>
|
|
172
|
+
Scope: <scope>
|
|
173
|
+
Mode: <mode>
|
|
174
|
+
Evidence baseline: <commands, commits, jobs, receipts>
|
|
175
|
+
|
|
176
|
+
## Narrative
|
|
177
|
+
<overall story: what happened, why, and remaining uncertainty>
|
|
178
|
+
|
|
179
|
+
## Change Units
|
|
180
|
+
### cu_001 - <title>
|
|
181
|
+
What: ...
|
|
182
|
+
Why: ...
|
|
183
|
+
Who / when: ...
|
|
184
|
+
Evidence: ...
|
|
185
|
+
Comments: ...
|
|
186
|
+
|
|
187
|
+
## Open Questions
|
|
188
|
+
- ...
|
|
189
|
+
|
|
190
|
+
## Evidence Package
|
|
191
|
+
- validation_receipt: <id or unavailable: reason>
|
|
192
|
+
- linked_jobs: [...]
|
|
193
|
+
- linked_receipts: [...]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Machine-readable shape:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"retrospective_id": "retro_<id>",
|
|
201
|
+
"schema_version": "retrospective-walk.v0.1.0",
|
|
202
|
+
"scope": "commit-range",
|
|
203
|
+
"target": "HEAD~5..HEAD",
|
|
204
|
+
"mode": "guided",
|
|
205
|
+
"created_at": "RFC3339",
|
|
206
|
+
"provenance_context": {
|
|
207
|
+
"commands": [],
|
|
208
|
+
"commits": [],
|
|
209
|
+
"jobs": [],
|
|
210
|
+
"receipts": [],
|
|
211
|
+
"episodes": []
|
|
212
|
+
},
|
|
213
|
+
"change_units": [],
|
|
214
|
+
"comments": [],
|
|
215
|
+
"narrative": "",
|
|
216
|
+
"open_questions": [],
|
|
217
|
+
"validation_receipt": {
|
|
218
|
+
"status": "minted | linked | unavailable",
|
|
219
|
+
"validation_id": "optional",
|
|
220
|
+
"receipt_id": "optional",
|
|
221
|
+
"reason": "optional"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Validation Receipts
|
|
227
|
+
|
|
228
|
+
Use existing gateway validation receipt machinery when the retrospective is
|
|
229
|
+
backed by a terminal validation run:
|
|
230
|
+
|
|
231
|
+
- For prior validation ids, call `validation_receipt` and link the receipt.
|
|
232
|
+
- For review or why-synthesis model jobs launched during the retrospective,
|
|
233
|
+
preserve job ids and retrieve receipts if those jobs are part of a validation
|
|
234
|
+
run that can mint one.
|
|
235
|
+
- If no receipt-capable validation run exists, set
|
|
236
|
+
`validation_receipt.status` to `unavailable` and include a reason. Do not
|
|
237
|
+
claim a receipt was minted when the current gateway surface only produced a
|
|
238
|
+
retrospective artifact.
|
|
239
|
+
|
|
240
|
+
## DAG Plan
|
|
241
|
+
|
|
242
|
+
Use this plan shape when embedding the retrospective in a DAG or episode:
|
|
243
|
+
|
|
244
|
+
```toml
|
|
245
|
+
[plan]
|
|
246
|
+
name = "retrospective-walk"
|
|
247
|
+
version = "0.1.0"
|
|
248
|
+
description = "Guided retrospective over changes with what/why/who and comment capture as evidence"
|
|
249
|
+
owner = "verivus-oss"
|
|
250
|
+
|
|
251
|
+
[[node]]
|
|
252
|
+
id = "resolve-scope"
|
|
253
|
+
type = "data"
|
|
254
|
+
outputs = ["change_units", "provenance_context"]
|
|
255
|
+
|
|
256
|
+
[[node]]
|
|
257
|
+
id = "analyze-changes"
|
|
258
|
+
type = "compute"
|
|
259
|
+
inputs = ["change_units", "provenance_context", "models_for_why"]
|
|
260
|
+
outputs = ["analyzed_units"]
|
|
261
|
+
|
|
262
|
+
[[node]]
|
|
263
|
+
id = "capture-comments"
|
|
264
|
+
type = "human-or-agent"
|
|
265
|
+
inputs = ["analyzed_units"]
|
|
266
|
+
outputs = ["comments"]
|
|
267
|
+
|
|
268
|
+
[[node]]
|
|
269
|
+
id = "synthesize-retrospective"
|
|
270
|
+
type = "compute"
|
|
271
|
+
inputs = ["analyzed_units", "comments", "provenance_context"]
|
|
272
|
+
outputs = ["retrospective_markdown", "retrospective_json"]
|
|
273
|
+
|
|
274
|
+
[[node]]
|
|
275
|
+
id = "emit-evidence"
|
|
276
|
+
type = "side-effect"
|
|
277
|
+
inputs = ["retrospective_json", "comments", "analyzed_units"]
|
|
278
|
+
outputs = ["validation_receipt_id"]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Only update a DAG or episode when the caller provided a parent context and an
|
|
282
|
+
available tool supports that write. Otherwise emit the retrospective and the
|
|
283
|
+
evidence package without side effects.
|
|
284
|
+
|
|
285
|
+
## Security And Isolation
|
|
286
|
+
|
|
287
|
+
- Prefer read-only evidence capture. Do not mutate user worktrees while walking
|
|
288
|
+
changes.
|
|
289
|
+
- Use a disposable isolated git worktree for model/tool operations that may edit
|
|
290
|
+
files, especially for `scope = worktree`.
|
|
291
|
+
- Keep secrets, local account names, and machine-specific paths out of
|
|
292
|
+
persisted comments and receipts unless the user explicitly requires them.
|
|
293
|
+
- Treat model-generated comments as model evidence, not human approval.
|
|
294
|
+
- Preserve provenance for every comment, job, receipt, and inferred rationale.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the llm-cli-gateway project.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [2.14.1] - 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`retrospective-walk` workflow skill.** Bundles a guided retrospective skill
|
|
12
|
+
for walking a human or agent through a diff, worktree, commit range, gateway
|
|
13
|
+
job, or episode reference with structured what/why/who analysis, comment
|
|
14
|
+
capture as evidence, machine-readable output, and validation-receipt links
|
|
15
|
+
when an existing receipt-capable validation run is available.
|
|
16
|
+
|
|
17
|
+
## [2.14.0] - 2026-07-03
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Durable instance-lease orphan recovery (#139).** The blanket
|
|
22
|
+
`markOrphanedOnStartup` sweep in the `AsyncJobManager` constructor rewrote
|
|
23
|
+
every `running` row to `orphaned`, so on a SHARED store (`backend =
|
|
24
|
+
"postgres"`) a fresh instance (especially an ephemeral stdio spawn)
|
|
25
|
+
transiently orphaned other live instances' in-flight jobs (a `running` ->
|
|
26
|
+
`orphaned` -> `completed` flap a poller can trip on). The sweep is now a
|
|
27
|
+
per-job fencing lease: each instance registers a lease and advances a
|
|
28
|
+
`jobs.lease_deadline` on every heartbeat, and the sweep orphans a
|
|
29
|
+
`queued`/`running` job only when its own `lease_deadline` has expired, never
|
|
30
|
+
because a different live instance started. Because heartbeat and sweep are
|
|
31
|
+
both `UPDATE`s on the same `jobs` rows, they serialize on the row lock
|
|
32
|
+
(Postgres READ COMMITTED) and are trivially serial under single-writer sqlite,
|
|
33
|
+
so no job whose owner heartbeated within the lease TTL is ever swept. A
|
|
34
|
+
genuinely stale-then-reviving owner self-heals to the correct terminal state
|
|
35
|
+
via the guarded `recordComplete` and a flight-recorder reconcile. The fix is
|
|
36
|
+
transport-aware (an extra `httpJobGrace` for no-pid http jobs, an advisory
|
|
37
|
+
never-vetoing `kill(pid,0)` for same-host process jobs), durable-admission is
|
|
38
|
+
fail-closed (a failed `recordStart`/`registerInstance` fails the request and
|
|
39
|
+
releases the limiter permit), and graceful shutdown drains in-flight terminal
|
|
40
|
+
writes before deregistering the lease. Additive schema (`jobs.owner_instance`,
|
|
41
|
+
`jobs.lease_deadline`, a `gateway_instances` table), auto-created on both
|
|
42
|
+
sqlite and postgres, no data migration.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`[persistence].ownsOrphanRecovery` is deprecated (#139).** The interim gate
|
|
47
|
+
(PR #140) that let one designated `postgres` instance own the blanket startup
|
|
48
|
+
sweep is superseded by the durable lease above, which is safe to run from
|
|
49
|
+
every instance. The flag is still parsed (so existing configs do not error)
|
|
50
|
+
and now emits a one-time deprecation warning; it no longer changes behaviour
|
|
51
|
+
and will be removed in a later release. New `[persistence]` knobs tune the
|
|
52
|
+
lease: `instanceHeartbeatMs` (15000), `instanceLeaseTtlMs` (90000),
|
|
53
|
+
`httpJobGraceMs` (300000), `orphanSweepIntervalMs` (30000), `instanceGcMs`
|
|
54
|
+
(3600000), validated so `instanceLeaseTtlMs >= 2 * instanceHeartbeatMs` and
|
|
55
|
+
`httpJobGraceMs >= instanceLeaseTtlMs`.
|
|
56
|
+
|
|
5
57
|
## [2.14.0-rc.1] - 2026-07-03: full-featured provider integration + security-review hardening
|
|
6
58
|
|
|
7
59
|
Release candidate. Every non-Cursor provider (Claude, Codex, Gemini, Grok,
|
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
> _"Without consultation, plans are frustrated, but with many counselors they succeed."_
|
|
10
10
|
> — Proverbs 15:22 (LSB)
|
|
11
11
|
|
|
12
|
-
**
|
|
12
|
+
**Secure local control plane for AI coding agents.**
|
|
13
13
|
|
|
14
|
-
`llm-cli-gateway`
|
|
14
|
+
`llm-cli-gateway` lets supported MCP clients operate Claude Code, Codex, Gemini/Antigravity, Grok Build, Mistral Vibe, Cognition Devin, Cursor Agent, and configured HTTP API providers through one user-owned gateway while preserving native CLI sessions, local credentials, durable async jobs, validation receipts, and review workflows.
|
|
15
15
|
|
|
16
|
-
**Why developers try it:**
|
|
16
|
+
**Why developers try it:** use the client you are already in to delegate work to local coding agents, scope remote execution to registered workspaces, gate risky actions, survive disconnects, and collect auditable review evidence without turning those agents into a generic chat proxy.
|
|
17
17
|
|
|
18
18
|
**Current signals:** CI and security workflows pass on `main`, OpenSSF Scorecard is published, OpenSSF Best Practices is passing, releases use Sigstore signing, and the package is MIT licensed.
|
|
19
19
|
|
|
@@ -38,7 +38,7 @@ Or use directly with `npx` from an MCP client:
|
|
|
38
38
|
|
|
39
39
|
## What It Provides Today
|
|
40
40
|
|
|
41
|
-
`llm-cli-gateway` is a single-user MCP
|
|
41
|
+
`llm-cli-gateway` is a single-user MCP control plane for operating AI coding agents from supported local or remote clients. It is more than a thin CLI wrapper:
|
|
42
42
|
|
|
43
43
|
- Runs registered provider CLIs and configured HTTP API providers through consistent sync and async MCP tools.
|
|
44
44
|
- Persists long-running jobs, supports restart-safe result collection, deduplication, cancellation, and sync-to-async deferral.
|
|
@@ -51,7 +51,7 @@ Or use directly with `npx` from an MCP client:
|
|
|
51
51
|
|
|
52
52
|
## Workflow Assets
|
|
53
53
|
|
|
54
|
-
The repo ships agent-ready workflow skills under [`.agents/skills`](.agents/skills) for async orchestration, session continuity, multi-LLM review, implement-review-fix loops, and secure approval-gated dispatch.
|
|
54
|
+
The repo ships agent-ready workflow skills under [`.agents/skills`](.agents/skills) for async orchestration, session continuity, multi-LLM review, implement-review-fix loops, retrospective evidence walks, and secure approval-gated dispatch. Seven caller-facing skills are bundled in the published npm package: `async-job-orchestration`, `multi-llm-review`, `session-workflow`, `secure-orchestration`, `implement-review-fix`, `retrospective-walk`, and `public-demo-session`. Machine-readable DAG-TOML plans live under [`docs/plans`](docs/plans) and [`setup/install-plan.dag.toml`](setup/install-plan.dag.toml) for workflows that need deterministic sequencing and verification gates.
|
|
55
55
|
|
|
56
56
|
The next documentation focus is provider-specific skill and DAG-TOML pairs for each outbound CLI and API-provider family: Claude, Codex, Gemini/Antigravity, Grok, Mistral Vibe, Devin, Cursor Agent, OpenAI-compatible endpoints, Anthropic Messages, and xAI Responses. The implementation plan is tracked in [`docs/plans/provider-workflow-assets.dag.toml`](docs/plans/provider-workflow-assets.dag.toml), with each provider asset expected to cover install/login checks or token-env checks, session behavior, approval modes, cache/telemetry surfaces, failure modes, and a smoke-test gate.
|
|
57
57
|
|
|
@@ -69,7 +69,7 @@ The next documentation focus is provider-specific skill and DAG-TOML pairs for e
|
|
|
69
69
|
|
|
70
70
|
## Personal MCP Appliance
|
|
71
71
|
|
|
72
|
-
The personal-appliance contract keeps that surface intentionally narrow: one trusted user runs the gateway on a machine or volume they own, connects one MCP endpoint, and
|
|
72
|
+
The personal-appliance contract keeps that surface intentionally narrow: one trusted user runs the gateway on a machine or volume they own, connects one MCP endpoint, and lets supported clients operate local coding agents through workspace-scoped, approval-gated, auditable requests.
|
|
73
73
|
|
|
74
74
|
The product contract is documented in [docs/personal-mcp/PRODUCT_CONTRACT.md](docs/personal-mcp/PRODUCT_CONTRACT.md). It defines the single-user scope, security posture, target support matrix, and provider-support verification gates. Public setup guides must not claim ChatGPT, Claude web, Claude Desktop, Codex, Gemini CLI, Gemini web, or Grok inbound support until the corresponding provider/client path has been verified.
|
|
75
75
|
|
|
@@ -659,7 +659,7 @@ Every async job is persisted to a job store as it transitions through running
|
|
|
659
659
|
|
|
660
660
|
- **Re-issuing a request is safe.** Identical `*_request` / `*_request_async` calls within the dedup window (default 1 hour) short-circuit onto the existing running or completed job — the caller gets back the same job ID instead of starting a duplicate run. This directly fixes the "agent times out polling, re-issues, and the whole job starts over" failure mode.
|
|
661
661
|
- **`llm_job_status` and `llm_job_result` work across gateway restarts.** Job rows live for 30 days by default; callers can collect results long after the in-memory cache has evicted them.
|
|
662
|
-
- **
|
|
662
|
+
- **A job is marked `orphaned` only when its owning gateway instance is provably gone**, never because another instance restarted. Each instance holds a periodic heartbeat lease and stamps every job it owns; the recovery sweep orphans a `queued`/`running` job only when that job's own lease has expired. On a shared store (`backend = "postgres"`) this means a fresh instance never orphans another live instance's in-flight jobs. The captured partial output of a genuinely orphaned job remains readable, and a stale-then-reviving owner that later finishes self-heals to the correct terminal state (issue #139).
|
|
663
663
|
- **Pass `forceRefresh: true`** on any request tool to bypass dedup and force a fresh CLI run.
|
|
664
664
|
|
|
665
665
|
##### Persistence configuration
|
|
@@ -674,6 +674,18 @@ path = "~/.llm-cli-gateway/logs.db" # for sqlite
|
|
|
674
674
|
retentionDays = 30
|
|
675
675
|
dedupWindowMs = 3600000
|
|
676
676
|
acknowledgeEphemeral = false # required to enable async tools with memory backend
|
|
677
|
+
|
|
678
|
+
# Issue #139 durable orphan-recovery lease (defaults shown). Each instance
|
|
679
|
+
# advances a per-job lease on every heartbeat; the sweep orphans a job only
|
|
680
|
+
# after its own lease expires, so a fresh instance never orphans another live
|
|
681
|
+
# instance's jobs on a shared store. Validated: leaseTtl >= 2*heartbeat and
|
|
682
|
+
# httpJobGrace >= leaseTtl.
|
|
683
|
+
instanceHeartbeatMs = 15000 # heartbeat cadence
|
|
684
|
+
instanceLeaseTtlMs = 90000 # per-job lease TTL (6x heartbeat)
|
|
685
|
+
httpJobGraceMs = 300000 # extra grace for no-pid http jobs (5 min)
|
|
686
|
+
orphanSweepIntervalMs = 30000 # reaper cadence
|
|
687
|
+
instanceGcMs = 3600000 # gateway_instances GC horizon
|
|
688
|
+
# ownsOrphanRecovery = false # DEPRECATED (#139): superseded by the lease; parsed + warned, no longer used
|
|
677
689
|
```
|
|
678
690
|
|
|
679
691
|
Backends:
|
|
@@ -5,6 +5,14 @@ import { type FlightRecorderLike } from "./flight-recorder.js";
|
|
|
5
5
|
import type { ValidationRunStore } from "./job-store.js";
|
|
6
6
|
import { type ApiProvider, type ApiRequest, type ApiUsage } from "./api-provider.js";
|
|
7
7
|
import { type JobLimitsConfig } from "./config.js";
|
|
8
|
+
export interface LeaseRuntimeConfig {
|
|
9
|
+
instanceHeartbeatMs: number;
|
|
10
|
+
instanceLeaseTtlMs: number;
|
|
11
|
+
httpJobGraceMs: number;
|
|
12
|
+
orphanSweepIntervalMs: number;
|
|
13
|
+
instanceGcMs: number;
|
|
14
|
+
role?: string;
|
|
15
|
+
}
|
|
8
16
|
export declare function extractApiHttpStatus(error: unknown): number | null;
|
|
9
17
|
export declare function extractApiErrorBody(error: unknown): string | undefined;
|
|
10
18
|
export type LlmCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin" | "cursor";
|
|
@@ -102,7 +110,34 @@ export declare class AsyncJobManager {
|
|
|
102
110
|
private readonly limiter;
|
|
103
111
|
private readonly completedJobMemoryTtlMs;
|
|
104
112
|
private readonly maxJobOutputBytes;
|
|
105
|
-
|
|
113
|
+
private readonly instanceId;
|
|
114
|
+
private readonly hostname;
|
|
115
|
+
private readonly instancePid;
|
|
116
|
+
private readonly lease;
|
|
117
|
+
private heartbeatTimer;
|
|
118
|
+
private sweepTimer;
|
|
119
|
+
private durableAdmission;
|
|
120
|
+
private consecutiveHeartbeatFailures;
|
|
121
|
+
private skipSweepThisCycle;
|
|
122
|
+
private nextHeartbeatExpectedAt;
|
|
123
|
+
private disposed;
|
|
124
|
+
private readonly pendingWrites;
|
|
125
|
+
constructor(logger?: Logger, onJobComplete?: ((cli: JobProvider, durationMs: number, success: boolean) => void) | undefined, store?: JobStore | null, flightRecorder?: FlightRecorderLike, limits?: JobLimitsConfig, _deprecatedOwnsOrphanRecovery?: boolean, leaseConfig?: LeaseRuntimeConfig);
|
|
126
|
+
canAdmitDurableJobs(): boolean;
|
|
127
|
+
private assertDurableAdmission;
|
|
128
|
+
private trackPendingWrite;
|
|
129
|
+
dispose(opts?: {
|
|
130
|
+
timeoutMs?: number;
|
|
131
|
+
}): Promise<void>;
|
|
132
|
+
getInstanceId(): string;
|
|
133
|
+
private startHeartbeat;
|
|
134
|
+
private onHeartbeatTick;
|
|
135
|
+
runOrphanSweepNow(): void;
|
|
136
|
+
private startReaper;
|
|
137
|
+
private runOrphanSweep;
|
|
138
|
+
private confirmLiveProcessCandidates;
|
|
139
|
+
private recordStartOrFailClosed;
|
|
140
|
+
private markRunningDurable;
|
|
106
141
|
private buildOrphanFlightResult;
|
|
107
142
|
checkStalledJobs(now?: number): void;
|
|
108
143
|
hasStore(): boolean;
|