@ctrl-spc/cs 0.7.6 → 0.7.7
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/dist/panel3/prompt.js +20 -1
- package/dist/panel3/run.js +22 -9
- package/dist/panel3/tools.js +14 -4
- package/package.json +2 -2
package/dist/panel3/prompt.js
CHANGED
|
@@ -550,6 +550,23 @@ export function levelOnePrompt(cardTitle, messages, produced, attachments = [],
|
|
|
550
550
|
* Every child it starts, and every grandchild THEY start, would be working
|
|
551
551
|
* from nothing but a responsibility sentence somebody else wrote down.
|
|
552
552
|
*/
|
|
553
|
+
export const OWNER_COMPLETION_RULES = [
|
|
554
|
+
'ASSIGNMENT STATUS — THIS OVERRIDES OLDER ENDING INSTRUCTIONS IN YOUR BRIEF.',
|
|
555
|
+
'A reply or process exit does not finish the assignment. Keep working through all remaining',
|
|
556
|
+
'required steps and checks. Status questions never cancel work already in progress.',
|
|
557
|
+
'When you yield to workers, write_report with what remains and exit; you resume with results.',
|
|
558
|
+
'When no worker is active and work remains, continue it yourself or dispatch it. If you exit',
|
|
559
|
+
'without declaring completion, the product starts you again to carry on.',
|
|
560
|
+
'Only when the latest user assignment is fulfilled, call write_report with work_complete=true',
|
|
561
|
+
'and a report explaining what fulfilled it. Check all required steps and verification first.',
|
|
562
|
+
'Then give the final answer and exit. Do not declare completion for an interim status answer.',
|
|
563
|
+
'A failed or skipped required step is unfinished work, even if all workers have returned.',
|
|
564
|
+
'Resolve it or ask for the real required intervention; never convert it into a completion caveat.',
|
|
565
|
+
'If a required user action prevents progress, use ask_question and stop. It must name the actual',
|
|
566
|
+
'approval, credential or manual action needed. Never ask just because no agent is running.',
|
|
567
|
+
'A handover explicitly requested by the person may finish that request without finishing a',
|
|
568
|
+
'feature. Say exactly what was handed over and do not claim the unfinished feature is done.',
|
|
569
|
+
].join('\n');
|
|
553
570
|
export function workBrief(level, responsibility, boundary, workItemId, attachments = [], codebase) {
|
|
554
571
|
/* ═══ ONLY WHEN A WORKFLOW IS ACTUALLY ON THE CARD. ═══ An owner is not
|
|
555
572
|
instructed about a process that is not there, which is the rule
|
|
@@ -562,6 +579,7 @@ export function workBrief(level, responsibility, boundary, workItemId, attachmen
|
|
|
562
579
|
? [
|
|
563
580
|
'You see one thing a person asked for through to done. Keep the whole picture and stay',
|
|
564
581
|
'available to the person while the work moves.',
|
|
582
|
+
OWNER_COMPLETION_RULES,
|
|
565
583
|
'',
|
|
566
584
|
/* ═══ THE FIRST THING, EVERY TIME, AND IT IS NOT THE REPLY. ═══ Nothing
|
|
567
585
|
an owner writes reaches the person until its process exits, and a turn
|
|
@@ -836,7 +854,7 @@ export const landingOutcomeContext = (landing) => {
|
|
|
836
854
|
'They chose neither of the two answers, so nothing was merged and the work is still on '
|
|
837
855
|
+ `${landing.branch ?? 'its branch'}.`,
|
|
838
856
|
'Read what they actually wrote and answer it. If they still want the work put back, offer the',
|
|
839
|
-
'ending again with `offer_ending` rather than
|
|
857
|
+
'ending again with `offer_ending` rather than declaring completion: an',
|
|
840
858
|
'offer leaves it with them.',
|
|
841
859
|
];
|
|
842
860
|
}
|
|
@@ -915,6 +933,7 @@ landing = null) {
|
|
|
915
933
|
});
|
|
916
934
|
return [
|
|
917
935
|
'YOU OWN THIS CTRL+SPC CONVERSATION.',
|
|
936
|
+
OWNER_COMPLETION_RULES,
|
|
918
937
|
'You are this conversation\'s one owner, started for one serialized activation. Speak directly to the',
|
|
919
938
|
'person as CTRL+SPC. The launcher is finished and cannot answer or receive an escalation.',
|
|
920
939
|
'',
|
package/dist/panel3/run.js
CHANGED
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
// `import type` loses its v3 header in the published `dist/`.
|
|
134
134
|
import { out, returned, signedInClient } from './client.js';
|
|
135
135
|
import { readableWriteError } from '../firewall.js';
|
|
136
|
-
import { answerPrompt, escalationPrompt, levelOnePrompt, ownerActivationPrompt, readBackPrompt, landingOutcomeContext, presentedArtifactAnswerContext, resumePrompt, retryPrompt, standingRules, whatWasAttached, workingRules, } from './prompt.js';
|
|
136
|
+
import { answerPrompt, escalationPrompt, levelOnePrompt, ownerActivationPrompt, readBackPrompt, landingOutcomeContext, presentedArtifactAnswerContext, resumePrompt, retryPrompt, standingRules, whatWasAttached, workingRules, OWNER_COMPLETION_RULES, } from './prompt.js';
|
|
137
137
|
import { ASK_CONTENT_COLUMNS, attachmentLine, gitRulesFor, loadAttachments, loadOutputNames, outputOf, recordBaseProtection, standingRulesFor, withAskContent, } from './show.js';
|
|
138
138
|
import { forgetSecrets, redactSecrets } from './secrets.js';
|
|
139
139
|
import { sayListening, stopListening } from './presence.js';
|
|
@@ -1950,8 +1950,17 @@ export function ownerContinuationPrompt(brief, report, events, children, deliver
|
|
|
1950
1950
|
const messages = events.filter((event) => event.kind === 'user' && event.needsReply);
|
|
1951
1951
|
return [
|
|
1952
1952
|
'CONTINUE THE CTRL+SPC CONVERSATION YOU ALREADY OWN.',
|
|
1953
|
+
OWNER_COMPLETION_RULES,
|
|
1953
1954
|
'Use only the new facts below as the new turn. Keep coordinating the whole card and speak as CTRL+SPC.',
|
|
1954
1955
|
'',
|
|
1956
|
+
...(messages.length === 0 && delivered === null ? [
|
|
1957
|
+
'WHY YOU WERE STARTED AGAIN',
|
|
1958
|
+
'The assignment is still open. Your previous final reply did not record completion.',
|
|
1959
|
+
'Do not repeat a completion reply by itself. If the assignment is fulfilled, call',
|
|
1960
|
+
'write_report with work_complete=true NOW, then give the final answer. Otherwise do the',
|
|
1961
|
+
'remaining work or resolve the new worker results below. No new user message is needed.',
|
|
1962
|
+
'',
|
|
1963
|
+
] : []),
|
|
1955
1964
|
'NEW USER MESSAGES',
|
|
1956
1965
|
...(messages.length === 0
|
|
1957
1966
|
? ['None.']
|
|
@@ -2425,15 +2434,17 @@ export async function reconcileOwnerSessions(client, machineId, _machineHarness,
|
|
|
2425
2434
|
async function killStopped(client, machineId) {
|
|
2426
2435
|
const stopped = await returned(client
|
|
2427
2436
|
.from('panel3_runs')
|
|
2428
|
-
.select('id, card_id, pid, started_at, resumed_at')
|
|
2437
|
+
.select('id, card_id, pid, state, started_at, resumed_at')
|
|
2429
2438
|
.eq('machine_id', machineId)
|
|
2430
|
-
.in('state', ENDED_BY_THE_PERSON)
|
|
2439
|
+
.in('state', [...ENDED_BY_THE_PERSON, 'finished', 'failed'])
|
|
2431
2440
|
.not('pid', 'is', null), 'read', 'the runs on this machine that are not coming back');
|
|
2432
2441
|
if (stopped.length === 0)
|
|
2433
2442
|
return;
|
|
2434
2443
|
const booted = bootedAt();
|
|
2435
2444
|
for (const run of stopped) {
|
|
2436
2445
|
if (runProcessIsAlive(run, booted)) {
|
|
2446
|
+
if (!ENDED_BY_THE_PERSON.includes(run.state))
|
|
2447
|
+
continue;
|
|
2437
2448
|
try {
|
|
2438
2449
|
process.kill(run.pid);
|
|
2439
2450
|
out(`stopped run ${run.id} card ${run.card_id} killed pid ${run.pid}`);
|
|
@@ -2455,11 +2466,11 @@ async function killStopped(client, machineId) {
|
|
|
2455
2466
|
await returned(client
|
|
2456
2467
|
.from('panel3_runs')
|
|
2457
2468
|
.update({ pid: null })
|
|
2458
|
-
//
|
|
2459
|
-
//
|
|
2460
|
-
// keeps that true if something one day does.
|
|
2469
|
+
// A finished owner may activate again between this read and write.
|
|
2470
|
+
// Only acknowledge the exact process whose exit was observed.
|
|
2461
2471
|
.eq('id', run.id)
|
|
2462
|
-
.
|
|
2472
|
+
.eq('pid', run.pid)
|
|
2473
|
+
.in('state', [...ENDED_BY_THE_PERSON, 'finished', 'failed'])
|
|
2463
2474
|
.select('id'), 'clear the process id of', `run ${run.id}`);
|
|
2464
2475
|
}
|
|
2465
2476
|
catch (error) {
|
|
@@ -2562,7 +2573,9 @@ async function recoverStranded(client, tools, machineId, mine, hold) {
|
|
|
2562
2573
|
await writeAnswer(client, run.id, run.card_id, null, run.process_token ?? undefined);
|
|
2563
2574
|
}
|
|
2564
2575
|
else {
|
|
2565
|
-
|
|
2576
|
+
// This process belonged to a previous daemon. Use the fenced recovery
|
|
2577
|
+
// claim, not a failed-attempt retry whose budget may already be spent.
|
|
2578
|
+
const resumed = await activateOwner(client, tools, machineId, run.id, run.process_token, null);
|
|
2566
2579
|
if (resumed)
|
|
2567
2580
|
hold(run.id, resumed.settled);
|
|
2568
2581
|
}
|
|
@@ -2914,7 +2927,7 @@ export async function sweepFinishedWorktrees(client) {
|
|
|
2914
2927
|
const live = new Set((await returned(client.from('panel3_runs')
|
|
2915
2928
|
.select('card_id')
|
|
2916
2929
|
.in('card_id', cards)
|
|
2917
|
-
.
|
|
2930
|
+
.or('ended_at.is.null,pid.not.is.null'), 'read', 'which of those cards still have an agent going')).map((row) => row.card_id));
|
|
2918
2931
|
const touched = new Set();
|
|
2919
2932
|
for (const one of held) {
|
|
2920
2933
|
const cardIds = cardOfFolder.get(one.folder) ?? [];
|
package/dist/panel3/tools.js
CHANGED
|
@@ -2481,11 +2481,21 @@ const TOOLS = [
|
|
|
2481
2481
|
description: 'Write down what is true NOW: what you have done, what you decided and why, and what is still '
|
|
2482
2482
|
+ 'open. It REPLACES your last report rather than adding to it, and it is what you are handed if '
|
|
2483
2483
|
+ 'you are respawned, so write it as the thing you would want to read to carry on. Write it as '
|
|
2484
|
-
+ 'you go, not only at the end. '
|
|
2484
|
+
+ 'you go, not only at the end. The conversation owner MUST set work_complete=true only when '
|
|
2485
|
+
+ 'the latest user assignment is fulfilled, including its required steps and checks. A status '
|
|
2486
|
+
+ 'reply is not completion. This records intent; Done waits for all agents to exit. '
|
|
2485
2487
|
+ FIREWALL_WRITING_RULE,
|
|
2486
|
-
input: { report: z.string().min(1) },
|
|
2487
|
-
handler: async ({ client, runId, processToken }, args) => {
|
|
2488
|
-
const { report } = args;
|
|
2488
|
+
input: { report: z.string().min(1), work_complete: z.boolean().optional() },
|
|
2489
|
+
handler: async ({ client, runId, processToken, isOwner }, args) => {
|
|
2490
|
+
const { report, work_complete } = args;
|
|
2491
|
+
if (work_complete) {
|
|
2492
|
+
if (!isOwner || processToken === undefined)
|
|
2493
|
+
throw new Error('Only the conversation owner can declare the assignment complete.');
|
|
2494
|
+
await whileRunning(client.rpc('panel3_request_completion', {
|
|
2495
|
+
p_run_id: runId, p_process_token: processToken, p_summary: report,
|
|
2496
|
+
}), runId, 'declare completion of');
|
|
2497
|
+
return 'Completion recorded. Give your final answer and exit. Done waits until all processes have exited.';
|
|
2498
|
+
}
|
|
2489
2499
|
await whileRunning(processToken === undefined
|
|
2490
2500
|
? client.from('panel3_runs').update({ report }).eq('id', runId)
|
|
2491
2501
|
.in('state', STILL_WRITING).is('ended_at', null).select('id')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctrl-spc/cs",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "CTRL+SPC
|
|
3
|
+
"version": "0.7.7",
|
|
4
|
+
"description": "CTRL+SPC \u2014 minimal, reliable per-machine agent presence. Sign-in, auto-start, agent detection, heartbeat presence, and ping acknowledgement.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
7
7
|
},
|