@rubytech/create-maxy-code 0.1.491 → 0.1.493
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/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +46 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.js +135 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/pages-output-dir.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +7 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +26 -14
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.d.ts +19 -0
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.js +185 -0
- package/payload/platform/lib/storage-broker/dist/pages-output-dir.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +65 -0
- package/payload/platform/lib/storage-broker/src/__tests__/pages-output-dir.test.ts +167 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +40 -16
- package/payload/platform/lib/storage-broker/src/pages-output-dir.ts +194 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +99 -0
- package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +108 -0
- package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +18 -6
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +13 -1
- package/payload/platform/plugins/cloudflare/bin/__tests__/portal-brand-css.test.sh +1 -1
- package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +22 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +111 -3
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +13 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +8 -4
- package/payload/platform/plugins/cloudflare/references/hosting-sites.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +4 -4
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +12 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +8 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +6 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +35 -6
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +62 -1
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +9 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/skill-staleness-audit.d.ts +80 -0
- package/payload/platform/services/claude-session-manager/dist/skill-staleness-audit.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/skill-staleness-audit.js +216 -0
- package/payload/platform/services/claude-session-manager/dist/skill-staleness-audit.js.map +1 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +5 -1
- package/payload/platform/templates/agents/passive/IDENTITY.md +2 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +2 -0
- package/payload/platform/templates/specialists/agents/citation-auditor.md +1 -0
- package/payload/platform/templates/specialists/agents/coding-assistant.md +1 -0
- package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +3 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +1 -0
- package/payload/platform/templates/specialists/agents/data-manager.md +1 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +1 -0
- package/payload/platform/templates/specialists/agents/librarian.md +1 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +1 -0
- package/payload/platform/templates/specialists/agents/public-session-reviewer.md +1 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +3 -0
- package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +1 -0
- package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +3 -0
- package/payload/server/{chunk-KZFE3MOY.js → chunk-RPUTKSAJ.js} +134 -16
- package/payload/server/public/assets/{chat-DWvYv9jq.js → chat-1SSmFsjd.js} +1 -1
- package/payload/server/public/assets/{operator-CldXXilj.js → operator-Cxq9TxaH.js} +1 -1
- package/payload/server/public/assets/{page-BIwdWXnv.js → page-DBHzC59_.js} +1 -1
- package/payload/server/public/assets/{public-BKQEQGPE.js → public-jIzvdjt_.js} +1 -1
- package/payload/server/public/chat.html +2 -2
- package/payload/server/public/operator.html +2 -2
- package/payload/server/public/public.html +2 -2
- package/payload/server/server.js +40 -31
- package/payload/server/{src-UYDFMWF4.js → src-JN6PIAJN.js} +1 -1
|
@@ -309,13 +309,85 @@ function generationClient() {
|
|
|
309
309
|
return []
|
|
310
310
|
}
|
|
311
311
|
if (/DELETE FROM directory\b/i.test(sql)) {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
const g = Number(params[1])
|
|
313
|
+
// Two DELETE shapes reach this client: the post-flip sweep removes
|
|
314
|
+
// every OTHER generation (`generation != ?`); the pre-stage clear
|
|
315
|
+
// (Task 1923) removes exactly THIS generation (`generation = ?`).
|
|
316
|
+
if (/generation\s*!=\s*\?/i.test(sql)) {
|
|
317
|
+
for (let i = rows.length - 1; i >= 0; i--) if (rows[i].generation !== g) rows.splice(i, 1)
|
|
318
|
+
} else {
|
|
319
|
+
for (let i = rows.length - 1; i >= 0; i--) if (rows[i].generation === g) rows.splice(i, 1)
|
|
315
320
|
}
|
|
316
321
|
return []
|
|
317
322
|
}
|
|
318
323
|
if (/SELECT COUNT/i.test(sql)) {
|
|
324
|
+
// The pre-stage orphan count (Task 1923) filters `generation = ?`; the
|
|
325
|
+
// verify count joins the pointer. Only the former binds a generation.
|
|
326
|
+
if (/generation\s*=\s*\?/i.test(sql)) {
|
|
327
|
+
const g = Number(params[1])
|
|
328
|
+
return [{ n: rows.filter((r) => r.generation === g).length }]
|
|
329
|
+
}
|
|
330
|
+
return [{ n: rows.filter((r) => r.generation === pointer).length }]
|
|
331
|
+
}
|
|
332
|
+
return []
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// The wedge Task 1923 fixes: a stage that half-completed on a D1 500 leaves
|
|
338
|
+
// orphan rows at the NEXT generation while the pointer stays at current. This
|
|
339
|
+
// client models the one property that turns that into a permanent freeze — the
|
|
340
|
+
// D1 UNIQUE(accountId, relPath, generation) constraint — by throwing on a
|
|
341
|
+
// colliding INSERT, exactly as every later cycle's first re-stage did.
|
|
342
|
+
function wedgeableClient(seed: { pointer: number; current?: string[]; orphans: string[] }) {
|
|
343
|
+
const calls: { sql: string; params: unknown[] }[] = []
|
|
344
|
+
const rows: { accountId: string; relPath: string; generation: number }[] = []
|
|
345
|
+
let pointer: number | null = seed.pointer
|
|
346
|
+
const orphanGen = seed.pointer + 1
|
|
347
|
+
// The last good tree the pointer names, present in a real wedge alongside the
|
|
348
|
+
// orphans. Seeding it proves the pre-stage clear scopes to `next` and leaves
|
|
349
|
+
// the reader-visible current generation untouched.
|
|
350
|
+
for (const relPath of seed.current ?? []) rows.push({ accountId: 'acc-1', relPath, generation: seed.pointer })
|
|
351
|
+
for (const relPath of seed.orphans) rows.push({ accountId: 'acc-1', relPath, generation: orphanGen })
|
|
352
|
+
return {
|
|
353
|
+
calls,
|
|
354
|
+
rows,
|
|
355
|
+
pointerValue: () => pointer,
|
|
356
|
+
async query(sql: string, params: unknown[] = []) {
|
|
357
|
+
calls.push({ sql, params })
|
|
358
|
+
if (/SELECT\s+currentGeneration/i.test(sql)) {
|
|
359
|
+
return pointer === null ? [] : [{ currentGeneration: pointer }]
|
|
360
|
+
}
|
|
361
|
+
if (/INSERT INTO directory_state|UPDATE directory_state/i.test(sql)) {
|
|
362
|
+
pointer = Number(params[1] ?? params[0])
|
|
363
|
+
return []
|
|
364
|
+
}
|
|
365
|
+
if (/INSERT INTO directory \(/i.test(sql)) {
|
|
366
|
+
const accountId = String(params[0])
|
|
367
|
+
const relPath = String(params[1])
|
|
368
|
+
const generation = Number(params[6])
|
|
369
|
+
if (rows.some((r) => r.accountId === accountId && r.relPath === relPath && r.generation === generation)) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
'd1 query failed: HTTP 500 — UNIQUE constraint failed: directory.accountId, directory.relPath, directory.generation',
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
rows.push({ accountId, relPath, generation })
|
|
375
|
+
return []
|
|
376
|
+
}
|
|
377
|
+
if (/DELETE FROM directory\b/i.test(sql)) {
|
|
378
|
+
const g = Number(params[1])
|
|
379
|
+
if (/generation\s*!=\s*\?/i.test(sql)) {
|
|
380
|
+
for (let i = rows.length - 1; i >= 0; i--) if (rows[i].generation !== g) rows.splice(i, 1)
|
|
381
|
+
} else {
|
|
382
|
+
for (let i = rows.length - 1; i >= 0; i--) if (rows[i].generation === g) rows.splice(i, 1)
|
|
383
|
+
}
|
|
384
|
+
return []
|
|
385
|
+
}
|
|
386
|
+
if (/SELECT COUNT/i.test(sql)) {
|
|
387
|
+
if (/generation\s*=\s*\?/i.test(sql)) {
|
|
388
|
+
const g = Number(params[1])
|
|
389
|
+
return [{ n: rows.filter((r) => r.generation === g).length }]
|
|
390
|
+
}
|
|
319
391
|
return [{ n: rows.filter((r) => r.generation === pointer).length }]
|
|
320
392
|
}
|
|
321
393
|
return []
|
|
@@ -323,6 +395,42 @@ function generationClient() {
|
|
|
323
395
|
}
|
|
324
396
|
}
|
|
325
397
|
|
|
398
|
+
describe('a partial stage self-heals (Task 1923)', () => {
|
|
399
|
+
it('clears orphans at the next generation, flips onto it, and logs stage-clear', async () => {
|
|
400
|
+
// Pointer at 5 naming the last good tree; three orphan rows already at
|
|
401
|
+
// generation 6 for relPaths the current walk re-stages ('output' is the
|
|
402
|
+
// walk's first row, so the current code's first INSERT is the one that
|
|
403
|
+
// collides). Without the pre-stage clear that INSERT throws on the UNIQUE
|
|
404
|
+
// constraint and the push fails, which is what froze gls-data for ~6 h on
|
|
405
|
+
// 2026-07-22.
|
|
406
|
+
const c = wedgeableClient({
|
|
407
|
+
pointer: 5,
|
|
408
|
+
current: ['output', 'output/report.pdf', 'quotes', 'quotes/CR2969'],
|
|
409
|
+
orphans: ['output', 'output/report.pdf', 'quotes'],
|
|
410
|
+
})
|
|
411
|
+
const lines: string[] = []
|
|
412
|
+
const r = await push(c, { log: (l: string) => lines.push(l) })
|
|
413
|
+
|
|
414
|
+
expect(r.rows).toBe(7)
|
|
415
|
+
expect(c.pointerValue()).toBe(6)
|
|
416
|
+
// Nothing survives at a generation the pointer does not name.
|
|
417
|
+
expect(c.rows.every((row) => row.generation === 6)).toBe(true)
|
|
418
|
+
const clear = lines.find((l) => l.includes('op=stage-clear'))!
|
|
419
|
+
expect(clear).toContain('account=acc-1')
|
|
420
|
+
expect(clear).toContain('generation=6')
|
|
421
|
+
// Exactly the 3 orphans at generation 6, not the 4 current-tree rows the
|
|
422
|
+
// pointer names: the clear scopes to `next`, never to a reader-visible row.
|
|
423
|
+
expect(clear).toContain('cleared=3')
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('emits no stage-clear line on a clean cycle with no orphans', async () => {
|
|
427
|
+
const c = wedgeableClient({ pointer: 5, orphans: [] })
|
|
428
|
+
const lines: string[] = []
|
|
429
|
+
await push(c, { log: (l: string) => lines.push(l) })
|
|
430
|
+
expect(lines.some((l) => l.includes('op=stage-clear'))).toBe(false)
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
|
+
|
|
326
434
|
describe('the index replace is atomic', () => {
|
|
327
435
|
it('never issues a delete before the pointer flip', async () => {
|
|
328
436
|
// The delete-then-insert window this replaces was Task 1842: a client
|
|
@@ -60,6 +60,7 @@ const deadHead = async () => {
|
|
|
60
60
|
throw new Error('ECONNREFUSED')
|
|
61
61
|
}
|
|
62
62
|
const errHead = async () => ({ ok: false, status: 502 })
|
|
63
|
+
const missingHead = async () => ({ ok: false, status: 404 })
|
|
63
64
|
|
|
64
65
|
describe('processIndexedDownload', () => {
|
|
65
66
|
it('returns a signed url for an indexed file when the device answers', async () => {
|
|
@@ -87,6 +88,18 @@ describe('processIndexedDownload', () => {
|
|
|
87
88
|
expect(r.payload.error).toBe('device-offline')
|
|
88
89
|
})
|
|
89
90
|
|
|
91
|
+
// A live device that answers 404 means the indexed row is stale: the file was
|
|
92
|
+
// renamed or removed while the index was frozen. That is "refresh the list",
|
|
93
|
+
// not "the device is down", so it must not collapse into device-offline.
|
|
94
|
+
it('reports file-missing when a reachable device 404s the path', async () => {
|
|
95
|
+
const lines: string[] = []
|
|
96
|
+
const r = await processIndexedDownload('sess-a', PATH, makeEnv(), (l) => lines.push(l), 1000, missingHead as never)
|
|
97
|
+
expect(r.status).toBe(404)
|
|
98
|
+
expect(r.payload.error).toBe('file-missing')
|
|
99
|
+
expect(r.url).toBeUndefined()
|
|
100
|
+
expect(lines.some((l) => l.includes('result=file-missing'))).toBe(true)
|
|
101
|
+
})
|
|
102
|
+
|
|
90
103
|
it('404s a path that is not in the index, without contacting the device', async () => {
|
|
91
104
|
let touched = false
|
|
92
105
|
const spy = async () => {
|
|
@@ -55,10 +55,14 @@ describe('schema.sql', () => {
|
|
|
55
55
|
})
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
// The
|
|
59
|
-
// the
|
|
60
|
-
//
|
|
61
|
-
//
|
|
58
|
+
// The template's flat shape and its `pages_build_output_dir = "."` have to agree,
|
|
59
|
+
// because the two together are what puts index.html at the served root. Until
|
|
60
|
+
// Task 1917 the broker handed wrangler the site root as the positional asset
|
|
61
|
+
// directory, which overrode the declared output dir, so a nested public/ served
|
|
62
|
+
// the site one level too deep and `/` returned 404 — the live failure Task 1774
|
|
63
|
+
// closed by flattening. Since 1917 the broker publishes the declared output dir,
|
|
64
|
+
// so a mismatch here no longer 404s; these assertions keep the shipped template
|
|
65
|
+
// on the one shape that is deployed and measured.
|
|
62
66
|
describe('template layout matches what the deploy publishes', () => {
|
|
63
67
|
it('has no public/ level', () => {
|
|
64
68
|
expect(existsSync(join(TEMPLATE, 'public'))).toBe(false)
|
|
@@ -33,7 +33,7 @@ If the site captures form data, add the `[[d1_databases]]` binding from `d1-data
|
|
|
33
33
|
|
|
34
34
|
## 3. Load the reused deploy token
|
|
35
35
|
|
|
36
|
-
> **On a multi-tenant install, a client account does not run this step at all.** After the 1631 storage isolation, a client's `cloudflare.env` carries no master, and `cf-token.sh` denies it the `pages` scope (`reason=not-house`) — correctly, since that master is account-wide. A client account publishes with the `storage-pages-deploy` tool, which runs the deploy house-side; the project must already be recorded to the account by the house (`storage-pages-adopt`), because the project-to-folder mapping is not derivable and is never guessed. The rest of this section applies to the **house account and single-tenant installs**, where the master still lives in the account secrets file. If you are a client account and you reached a `not-house` denial here, that is this branch, not a broken token: use the tool.
|
|
36
|
+
> **On a multi-tenant install, a client account does not run this step at all.** After the 1631 storage isolation, a client's `cloudflare.env` carries no master, and `cf-token.sh` denies it the `pages` scope (`reason=not-house`) — correctly, since that master is account-wide. A client account publishes with the `storage-pages-deploy` tool, which runs the deploy house-side; the project must already be recorded to the account by the house (`storage-pages-adopt`), because the project-to-folder mapping is not derivable and is never guessed. The rest of this section applies to the **house account and single-tenant installs**, where the master still lives in the account secrets file. If you are a client account and you reached a `not-house` denial here, that is this branch, not a broken token: use the tool. The tool reads `pages_build_output_dir` from the site root's `wrangler.toml` (or `wrangler.json`/`.jsonc`) and publishes that directory as the served root, which is the same asset root step 4 below passes positionally. A declared output dir that is missing fails the deploy naming the path rather than publishing its parent.
|
|
37
37
|
|
|
38
38
|
A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). The `pages` scope resolves the stable `<brand>-pages-d1` token, which carries both. Resolve it with the deterministic helper, which mints it once only if absent and reuses it thereafter. This is the one command; do not source the secrets file and hand the resulting `CLOUDFLARE_API_TOKEN` to `wrangler` — that variable holds the **master**, not the per-scope token, and `wrangler pages` then returns `Authentication error [code: 10000]`:
|
|
39
39
|
|
|
@@ -98,11 +98,11 @@ Copy `template/` to `<accountDir>/sites/<project>/` and fill `wrangler.toml`'s `
|
|
|
98
98
|
placeholder in the assembled tree.**
|
|
99
99
|
|
|
100
100
|
`sites/<project>/` is the only location the broker will publish: `resolveSiteDir` resolves
|
|
101
|
-
`<accountDir>/sites/<name>` and refuses anything else with `invalid-site`. The tree is flat
|
|
102
|
-
the deploy publishes the directory it is handed — `index.html`, `portal.css`, `portal.js`,
|
|
101
|
+
`<accountDir>/sites/<name>` and refuses anything else with `invalid-site`. The tree is flat: `index.html`, `portal.css`, `portal.js`,
|
|
103
102
|
`functions/` and `wrangler.toml` all sit at its root, with no nested asset level, and
|
|
104
|
-
`pages_build_output_dir` reads `"."` to match.
|
|
105
|
-
|
|
103
|
+
`pages_build_output_dir` reads `"."` to match. The deploy publishes whatever
|
|
104
|
+
directory `pages_build_output_dir` declares, so the two must agree; a nested asset level that the
|
|
105
|
+
config does not declare publishes the site one level too deep, which serves 404 at the domain root.
|
|
106
106
|
|
|
107
107
|
The deploy serves everything in that tree except a fixed ignore list — `functions/`, `_worker.js`,
|
|
108
108
|
`_redirects`, `_headers`, `_routes.json`, `node_modules`, `.git`, `.DS_Store`, `.wrangler` — and
|
package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts
CHANGED
|
@@ -171,6 +171,18 @@ export async function processIndexedDownload(
|
|
|
171
171
|
try {
|
|
172
172
|
const head = await fetchFn(url, { method: 'HEAD' })
|
|
173
173
|
if (!head.ok) {
|
|
174
|
+
if (head.status === 404) {
|
|
175
|
+
// The device answered, so it is not down: this row is stale. The file
|
|
176
|
+
// was renamed or removed while the index was frozen, and the device
|
|
177
|
+
// returns the same 404 for a path that is missing and one that is not
|
|
178
|
+
// exposed — so this cannot and must not try to tell those apart. The
|
|
179
|
+
// client's remedy is "refresh the list", not "wait for the device".
|
|
180
|
+
log(
|
|
181
|
+
`[data-portal] op=fetch-through owner=${q(session.ownerId)} path=${q(relPath)} ` +
|
|
182
|
+
`status=404 ms=${Date.now() - started} result=file-missing`,
|
|
183
|
+
)
|
|
184
|
+
return { status: 404, payload: { ok: false, error: 'file-missing' } }
|
|
185
|
+
}
|
|
174
186
|
log(
|
|
175
187
|
`[data-portal] op=fetch-through owner=${q(session.ownerId)} path=${q(relPath)} ` +
|
|
176
188
|
`status=${head.status} ms=${Date.now() - started} result=device-error`,
|
|
@@ -292,6 +292,14 @@
|
|
|
292
292
|
say('That file is on the device, which is not reachable right now. Try again shortly.');
|
|
293
293
|
return;
|
|
294
294
|
}
|
|
295
|
+
/* A reachable device that 404s a stale row sends error:'file-missing'.
|
|
296
|
+
The message says refresh, not wait. Keyed on the string, so the
|
|
297
|
+
edge's own not-found 404s (not-granted, absent, is-directory) carry
|
|
298
|
+
error:'not found' and keep the generic message below. */
|
|
299
|
+
if (res.d && res.d.error === 'file-missing') {
|
|
300
|
+
say('That file is no longer available under this name. Refresh the list and try again.');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
295
303
|
if (!res.d.ok || !res.d.url) {
|
|
296
304
|
say('That file could not be opened.');
|
|
297
305
|
return;
|
|
@@ -128,6 +128,12 @@ provision_account_dir() {
|
|
|
128
128
|
{ "type": "command", "command": "bash $HOOKS_PATH/post-tool-use-agent.sh" }
|
|
129
129
|
]
|
|
130
130
|
},
|
|
131
|
+
{
|
|
132
|
+
"matcher": "mcp__plugin_browser_browser__browser-pdf-save",
|
|
133
|
+
"hooks": [
|
|
134
|
+
{ "type": "command", "command": "bash $HOOKS_PATH/quote-render-pdf-conformance.sh" }
|
|
135
|
+
]
|
|
136
|
+
},
|
|
131
137
|
{
|
|
132
138
|
"matcher": "mcp__.*__.*-(export|import)-parse$",
|
|
133
139
|
"hooks": [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -2071,6 +2071,10 @@ export function buildHttpApp(deps) {
|
|
|
2071
2071
|
}
|
|
2072
2072
|
const byte = body.byte;
|
|
2073
2073
|
const trigger = typeof body.trigger === 'string' ? body.trigger : 'manual';
|
|
2074
|
+
// Task 1920 — the stop button sets escalate:true so that when ESC proves
|
|
2075
|
+
// ineffective (turn blocked in a tool/subagent call) the manager escalates
|
|
2076
|
+
// once to Ctrl-C. Absent/false keeps the ESC-only re-send path.
|
|
2077
|
+
const escalate = body.escalate === true;
|
|
2074
2078
|
const intId = `${sessionId.slice(0, 8)}-${start}`;
|
|
2075
2079
|
deps.logger(`[webchat-interrupt] op=interrupt-requested intId=${intId} sessionId=${sessionId.slice(0, 8)} trigger=${trigger}`);
|
|
2076
2080
|
const result = writeInterruptToPty(sessionId, byte, deps.logger);
|
|
@@ -2115,6 +2119,11 @@ export function buildHttpApp(deps) {
|
|
|
2115
2119
|
let lastSendAt = sentAt;
|
|
2116
2120
|
let escSends = 1; // the initial ESC already written above
|
|
2117
2121
|
let canResend = true;
|
|
2122
|
+
// Task 1920 — the one-shot escalation guard. Seed it from the initial byte:
|
|
2123
|
+
// if the pre-loop write above already sent Ctrl-C (byte='ctrl-c'), the loop
|
|
2124
|
+
// must not send a second one. The invariant is "exactly one Ctrl-C ever per
|
|
2125
|
+
// call" because a second SIGINT can exit the CLI and kill the session.
|
|
2126
|
+
let ctrlCSent = byte === 'ctrl-c';
|
|
2118
2127
|
while (Date.now() - sentAt < stopMs) {
|
|
2119
2128
|
const st = deps.watcher.getBySessionId(sessionId)?.status;
|
|
2120
2129
|
if (st !== 'busy') {
|
|
@@ -2122,14 +2131,34 @@ export function buildHttpApp(deps) {
|
|
|
2122
2131
|
break;
|
|
2123
2132
|
}
|
|
2124
2133
|
if (canResend && Date.now() - lastSendAt >= reescalateMs) {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2134
|
+
if (escalate) {
|
|
2135
|
+
// Task 1920 — ESC ran for a full sub-window while still busy, so it is
|
|
2136
|
+
// ineffective (turn blocked in a tool/subagent call). Escalate once to
|
|
2137
|
+
// Ctrl-C (SIGINT reaches the harness mid-tool-call); never a second
|
|
2138
|
+
// time — a second SIGINT can exit the CLI and kill the session. After
|
|
2139
|
+
// the one Ctrl-C, stop sending and just poll for busy-clear.
|
|
2140
|
+
if (!ctrlCSent) {
|
|
2141
|
+
const esc = writeInterruptToPty(sessionId, 'ctrl-c', deps.logger);
|
|
2142
|
+
lastSendAt = Date.now();
|
|
2143
|
+
if (esc.ok) {
|
|
2144
|
+
ctrlCSent = true;
|
|
2145
|
+
deps.logger(`[webchat-interrupt] op=interrupt-escalated intId=${intId} mechanism=ctrl-c sends=${escSends + 1}`);
|
|
2146
|
+
}
|
|
2147
|
+
else {
|
|
2148
|
+
canResend = false; // child vanished mid-interrupt; the next status read exits the loop
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2130
2151
|
}
|
|
2131
2152
|
else {
|
|
2132
|
-
|
|
2153
|
+
const re = writeInterruptToPty(sessionId, byte, deps.logger);
|
|
2154
|
+
lastSendAt = Date.now();
|
|
2155
|
+
if (re.ok) {
|
|
2156
|
+
escSends += 1;
|
|
2157
|
+
deps.logger(`[webchat-interrupt] op=interrupt-reescalated intId=${intId} sends=${escSends}`);
|
|
2158
|
+
}
|
|
2159
|
+
else {
|
|
2160
|
+
canResend = false; // child vanished mid-interrupt; the next status read exits the loop
|
|
2161
|
+
}
|
|
2133
2162
|
}
|
|
2134
2163
|
}
|
|
2135
2164
|
await new Promise((r) => setTimeout(r, 120));
|