toolroll 0.9.34 → 0.9.37
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/CHANGELOG.md +102 -0
- package/README.md +7 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/assignment.d.ts +1 -0
- package/dist/assignment.js +2 -2
- package/dist/browser-workspace.d.ts +3 -3
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/builder.js +4 -7
- package/dist/chat-acceptance.d.ts +2 -2
- package/dist/chat-actions.d.ts +18 -12
- package/dist/chat-actions.js +15 -65
- package/dist/chat-ask.js +5 -5
- package/dist/chat-decide.d.ts +14 -1
- package/dist/chat-decide.js +12 -6
- package/dist/chat-delivery-state.d.ts +12 -70
- package/dist/chat-delivery-state.js +17 -135
- package/dist/chat-delivery.js +58 -30
- package/dist/chat-flow.js +8 -6
- package/dist/chat-inbox.js +1 -1
- package/dist/coding-context.d.ts +2 -39
- package/dist/coding-context.js +2 -1
- package/dist/coding-handoff.d.ts +6 -25
- package/dist/coding-handoff.js +11 -5
- package/dist/coding-types.d.ts +2 -26
- package/dist/coding-workspace.d.ts +2 -0
- package/dist/coding-workspace.js +50 -14
- package/dist/contracts/acceptance-terms.d.ts +14 -0
- package/dist/contracts/acceptance-terms.js +16 -0
- package/dist/contracts/chat-actions.d.ts +328 -0
- package/dist/contracts/chat-actions.js +133 -0
- package/dist/contracts/chat-callback-rows.d.ts +59 -0
- package/dist/contracts/chat-callback-rows.js +46 -0
- package/dist/contracts/chat-content.d.ts +276 -0
- package/dist/contracts/chat-content.js +127 -0
- package/dist/contracts/chat-tables.d.ts +6 -0
- package/dist/contracts/chat-tables.js +136 -0
- package/dist/contracts/coding-context.d.ts +97 -0
- package/dist/contracts/coding-context.js +60 -0
- package/dist/contracts/coding-handoff.d.ts +44 -0
- package/dist/contracts/coding-handoff.js +47 -0
- package/dist/contracts/coding-workspace.d.ts +165 -0
- package/dist/contracts/coding-workspace.js +71 -0
- package/dist/contracts/contract.d.ts +40 -7
- package/dist/contracts/contract.js +155 -19
- package/dist/contracts/decision.d.ts +45 -0
- package/dist/contracts/decision.js +45 -0
- package/dist/contracts/discord-callback.d.ts +45 -0
- package/dist/contracts/discord-callback.js +34 -0
- package/dist/contracts/flow-card.d.ts +50 -0
- package/dist/contracts/flow-card.js +46 -0
- package/dist/contracts/flow-propose.d.ts +325 -0
- package/dist/contracts/flow-propose.js +41 -0
- package/dist/contracts/flow-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +19405 -0
- package/dist/contracts/flow.js +364 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -0
- package/dist/contracts/handoff.d.ts +91 -0
- package/dist/contracts/handoff.js +82 -0
- package/dist/contracts/lead-context.d.ts +165 -0
- package/dist/contracts/lead-context.js +73 -0
- package/dist/contracts/lead-tools.d.ts +1905 -0
- package/dist/contracts/lead-tools.js +265 -0
- package/dist/contracts/memory-pass.d.ts +158 -0
- package/dist/contracts/memory-pass.js +102 -0
- package/dist/contracts/project-knowledge.d.ts +126 -0
- package/dist/contracts/project-knowledge.js +93 -0
- package/dist/contracts/project-memory.d.ts +117 -0
- package/dist/contracts/project-memory.js +51 -0
- package/dist/contracts/project-skills.d.ts +121 -0
- package/dist/contracts/project-skills.js +76 -0
- package/dist/contracts/proof.d.ts +136 -0
- package/dist/contracts/proof.js +110 -0
- package/dist/contracts/review-findings.d.ts +46 -0
- package/dist/contracts/review-findings.js +48 -0
- package/dist/contracts/route.d.ts +484 -0
- package/dist/contracts/route.js +128 -0
- package/dist/contracts/scope.d.ts +105 -0
- package/dist/contracts/scope.js +114 -0
- package/dist/contracts/scout-report.d.ts +234 -0
- package/dist/contracts/scout-report.js +141 -0
- package/dist/contracts/slack-callback.d.ts +43 -0
- package/dist/contracts/slack-callback.js +35 -0
- package/dist/contracts/sort-answer.d.ts +83 -0
- package/dist/contracts/sort-answer.js +77 -0
- package/dist/contracts/stage-output.d.ts +127 -0
- package/dist/contracts/stage-output.js +166 -0
- package/dist/contracts/task-sizing.d.ts +24 -0
- package/dist/contracts/task-sizing.js +28 -0
- package/dist/contracts/teammate-turn.d.ts +43 -0
- package/dist/contracts/teammate-turn.js +51 -0
- package/dist/contracts/teams-callback.d.ts +14 -0
- package/dist/contracts/teams-callback.js +14 -0
- package/dist/contracts/telegram-callback.d.ts +120 -0
- package/dist/contracts/telegram-callback.js +97 -0
- package/dist/contracts/verification-receipt.d.ts +148 -0
- package/dist/contracts/verification-receipt.js +87 -0
- package/dist/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/demo.js +28 -11
- package/dist/discord-chat.js +31 -20
- package/dist/evidence.d.ts +3 -40
- package/dist/evidence.js +5 -1
- package/dist/flow-engine.js +14 -17
- package/dist/flow-gallery.d.ts +3 -2
- package/dist/flow-items.d.ts +4 -13
- package/dist/flow-items.js +1 -15
- package/dist/flow-send.d.ts +6 -37
- package/dist/flow-send.js +18 -14
- package/dist/flow-share.d.ts +10 -32
- package/dist/flow-share.js +72 -84
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-starters.d.ts +2 -1
- package/dist/flow-steps.js +3 -2
- package/dist/flow-triggers.d.ts +15 -72
- package/dist/flow-triggers.js +94 -83
- package/dist/flows.d.ts +37 -220
- package/dist/flows.js +356 -387
- package/dist/invoke.js +4 -4
- package/dist/lead-context.d.ts +4 -2
- package/dist/lead-context.js +24 -17
- package/dist/mate-tools.d.ts +4 -0
- package/dist/mate-tools.js +111 -308
- package/dist/mcp-connect.d.ts +27 -4
- package/dist/mcp-connect.js +92 -17
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/operate.js +31 -2
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- package/dist/plan.d.ts +5 -5
- package/dist/plan.js +5 -25
- package/dist/process-recovery-eligibility.d.ts +2 -2
- package/dist/process-recovery-eligibility.js +13 -8
- package/dist/project-knowledge.d.ts +16 -35
- package/dist/project-knowledge.js +42 -21
- package/dist/project-memory.d.ts +2 -28
- package/dist/project-memory.js +28 -13
- package/dist/project-skills.d.ts +16 -38
- package/dist/project-skills.js +41 -32
- package/dist/project-tools.d.ts +1 -0
- package/dist/project-tools.js +4 -4
- package/dist/proof.d.ts +15 -91
- package/dist/proof.js +90 -249
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- package/dist/result-evidence-readers.d.ts +42 -0
- package/dist/result-evidence-readers.js +91 -0
- package/dist/result-shots.d.ts +1 -1
- package/dist/result-shots.js +2 -1
- package/dist/review-switch.d.ts +3 -8
- package/dist/reviewer.d.ts +8 -12
- package/dist/reviewer.js +11 -35
- package/dist/scope.d.ts +21 -43
- package/dist/scope.js +42 -128
- package/dist/scout-report.d.ts +9 -194
- package/dist/scout-report.js +113 -264
- package/dist/scout.js +15 -11
- package/dist/serve.js +17 -101
- package/dist/slack-chat.js +26 -20
- package/dist/slack-state.js +2 -1
- package/dist/store.d.ts +9 -32
- package/dist/store.js +18 -15
- package/dist/structured-output.js +2 -1
- package/dist/subscription-chat.d.ts +4 -0
- package/dist/subscription-chat.js +4 -1
- package/dist/surface.js +8 -1
- package/dist/task-review-brief.d.ts +69 -0
- package/dist/task-review-brief.js +179 -0
- package/dist/task-sizing.d.ts +3 -27
- package/dist/task-sizing.js +11 -26
- package/dist/task-text.d.ts +0 -5
- package/dist/task-text.js +1 -3
- package/dist/teammate-question.js +7 -5
- package/dist/teammate-work.js +9 -5
- package/dist/teammates.d.ts +13 -64
- package/dist/teammates.js +32 -33
- package/dist/teams-chat.js +20 -11
- package/dist/telegram-flow.js +4 -3
- package/dist/telegram-mate.d.ts +4 -14
- package/dist/telegram-mate.js +8 -7
- package/dist/telegram-team.d.ts +2 -19
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +52 -42
- package/dist/text-limits.d.ts +212 -1
- package/dist/text-limits.js +214 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/dist/verification-evidence.js +24 -12
- package/package.json +1 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { assignmentChecksForRun, assignmentOf } from './assignment.js';
|
|
2
|
+
import { assignmentPresentationOf } from './assignment-presentation.js';
|
|
3
|
+
import { readVerifiedArtifact, readVerifiedProofForRun, reportShotsOf, scanForSecrets } from './evidence.js';
|
|
4
|
+
import { SCREENSHOT_CAPTURE, structuredHandoffView, terminalDiffView } from './result-evidence-readers.js';
|
|
5
|
+
import { FINDING_SEVERITIES } from './contracts/review-findings.js';
|
|
6
|
+
import { buildReviewOf } from './review-switch.js';
|
|
7
|
+
import { parseReport } from './scout-report.js';
|
|
8
|
+
import { sanitizeTranscriptLine } from './live.js';
|
|
9
|
+
import { redactSecretAssignments } from './builder.js';
|
|
10
|
+
import { parseExecutionPlanDocument } from './plan.js';
|
|
11
|
+
const MISSING = 'not recorded';
|
|
12
|
+
/** Redact before shortening so a clipped credential cannot escape recognition. Apply to every data string in both formats. */
|
|
13
|
+
function safeText(value, secrets, cap = 300) {
|
|
14
|
+
if (typeof value !== 'string' || !value.trim())
|
|
15
|
+
return MISSING;
|
|
16
|
+
const flat = value.replace(/\s+/g, ' ').trim();
|
|
17
|
+
const cleaned = sanitizeTranscriptLine(flat);
|
|
18
|
+
if (secrets.some(secret => secret !== '' && value.includes(secret)) ||
|
|
19
|
+
/\blt_[a-f0-9]{12}_[A-Za-z0-9_-]{43}\b|\bBearer\s+\S+/i.test(flat) ||
|
|
20
|
+
scanForSecrets(flat).length || redactSecretAssignments(flat) !== flat || cleaned.startsWith('[a credential-shaped'))
|
|
21
|
+
return '[redacted]';
|
|
22
|
+
return cleaned.length <= cap ? cleaned : `${cleaned.slice(0, cap - 1)}…`;
|
|
23
|
+
}
|
|
24
|
+
function verifiedText(root, artifact) {
|
|
25
|
+
if (!artifact || artifact.truncated || artifact.captureStatus === 'failed')
|
|
26
|
+
return null;
|
|
27
|
+
const read = readVerifiedArtifact(root, artifact);
|
|
28
|
+
return read.ok ? read.content.toString('utf8') : null;
|
|
29
|
+
}
|
|
30
|
+
/** Select only finished build/report attempts in the admitted family. An explicit run never crosses that boundary. */
|
|
31
|
+
export function taskReviewBrief(store, task, root, now, access, options = {}) {
|
|
32
|
+
const family = store.taskFamilyOf(task, access.repos, access.principal === 'operator' && access.includeUnplaced === true);
|
|
33
|
+
if (family === null)
|
|
34
|
+
return { ok: false, reason: 'not-found', message: 'That task is unavailable in your projects.' };
|
|
35
|
+
const runs = family.versions.flatMap(version => store.runsFor(version.refId))
|
|
36
|
+
.filter(run => (run.role === 'builder' || run.role === 'scout') && run.finishedAt !== null && run.outcome !== null)
|
|
37
|
+
.sort((a, b) => b.id - a.id);
|
|
38
|
+
const run = options.run === undefined ? runs[0] ?? null : runs.find(one => one.id === options.run) ?? null;
|
|
39
|
+
if (options.run !== undefined && run === null)
|
|
40
|
+
return { ok: false, reason: 'not-found', message: 'That finished build or report is unavailable for this task.' };
|
|
41
|
+
const assignment = assignmentOf(store, task, now, access, root);
|
|
42
|
+
const safe = (value, cap) => safeText(value, options.secrets ?? [], cap);
|
|
43
|
+
const sha = (value) => /^[a-f0-9]{40,64}$/.test(value ?? '') ? value.slice(0, 12) : MISSING;
|
|
44
|
+
const artifacts = run === null ? [] : store.artifactsFor(run.id);
|
|
45
|
+
// Pass only the inventory: this reader does not need to open the patch for a brief.
|
|
46
|
+
const statView = terminalDiffView(artifacts.filter(one => one.kind === 'diff-stat' && one.captureStatus !== 'failed'), root)?.stat;
|
|
47
|
+
const stat = statView && !('problem' in statView) ? statView : null;
|
|
48
|
+
const handoff = structuredHandoffView(artifacts, root, value => safe(value, 600));
|
|
49
|
+
const reportText = run?.role === 'scout' ? verifiedText(root, artifacts.find(one => one.kind === 'report')) : null;
|
|
50
|
+
const report = reportText === null ? null : parseReport(reportText, { stored: true });
|
|
51
|
+
const conclusion = handoff?.conclusion ?? (report?.ok ? report.report.summary : run?.handoff);
|
|
52
|
+
const duration = run?.finishedAt ? (Date.parse(run.finishedAt) - Date.parse(run.startedAt)) / 60_000 : NaN;
|
|
53
|
+
const recordedCheck = run === null ? null : store.runCheckFor(run.id);
|
|
54
|
+
const receipt = assignment?.receipt?.runId === run?.id ? assignment?.receipt : null;
|
|
55
|
+
// The shared assignment reader includes later checks on the same candidate. Its result wins;
|
|
56
|
+
// old named failures must not ride a newer passing (or differently failed) check.
|
|
57
|
+
const checks = receipt?.checks ?? (run === null ? null : assignmentChecksForRun(store, root, run.id, now));
|
|
58
|
+
const status = checks && checks.status !== 'unavailable' && checks.status !== 'not-run' ? checks.status : recordedCheck?.status ?? checks?.status ?? MISSING;
|
|
59
|
+
const exit = checks && checks.status === status ? checks.exitCode : recordedCheck?.exitCode;
|
|
60
|
+
const sameCheck = recordedCheck?.status === status && recordedCheck.exitCode === exit &&
|
|
61
|
+
(!checks?.logArtifactId || artifacts.some(one => one.id === checks.logArtifactId && one.kind === 'check-log'));
|
|
62
|
+
const progress = run === null ? null : store.checkProgress(run.id);
|
|
63
|
+
const failed = sameCheck ? [...new Set([
|
|
64
|
+
...recordedCheck.suites.filter(one => one.status === 'failed').map(one => safe(one.name)),
|
|
65
|
+
...Object.entries(progress?.suites ?? {}).filter(([, suite]) => suite.state === 'failed').map(([name, suite]) => `${name}: ${suite.failed} failed`),
|
|
66
|
+
])] : [];
|
|
67
|
+
const review = run === null ? null : buildReviewOf(store, run.id);
|
|
68
|
+
const findings = review === null ? [] : [...review.high, ...review.followUps].filter(one => one && FINDING_SEVERITIES.includes(one.severity));
|
|
69
|
+
const counts = Object.fromEntries(FINDING_SEVERITIES.map(severity => [severity, findings.filter(one => one.severity === severity).length]));
|
|
70
|
+
const shown = FINDING_SEVERITIES.flatMap(severity => severity === 'LOW' && !options.all ? [] : findings.filter(one => one.severity === severity))
|
|
71
|
+
.slice(0, 12).map(one => ({ severity: one.severity, file: safe(one.file), line: Number.isSafeInteger(one.line) ? one.line : 0, scenario: safe(one.scenario) }));
|
|
72
|
+
const omitted = Object.fromEntries(FINDING_SEVERITIES.map(severity => [severity, counts[severity] - shown.filter(one => one.severity === severity).length]));
|
|
73
|
+
const proof = run === null ? null : readVerifiedProofForRun(store, root, run.id);
|
|
74
|
+
const shots = artifacts.filter(one => one.kind === 'screenshot');
|
|
75
|
+
const reportShots = report?.ok && run !== null ? reportShotsOf(artifacts, root, run.id, report.report.images) : [];
|
|
76
|
+
const pathOf = (artifact) => SCREENSHOT_CAPTURE.exec(artifact.capture)?.[1] ??
|
|
77
|
+
(reportShots.some(one => one.artifactId === artifact.id) ? `evidence/${artifact.key}` : null);
|
|
78
|
+
const declared = proof?.ok ? proof.proof.screenshots.map(one => one.path) : [];
|
|
79
|
+
const paths = [...new Set([...declared, ...shots.flatMap(one => pathOf(one) ?? [])])];
|
|
80
|
+
const screenshots = paths.map(path => {
|
|
81
|
+
const artifact = shots.find(one => pathOf(one) === path);
|
|
82
|
+
return { path: safe(path), bytes: artifact && artifact.captureStatus !== 'failed' && readVerifiedArtifact(root, artifact).ok ? artifact.bytesStored : MISSING };
|
|
83
|
+
});
|
|
84
|
+
const plan = run === null ? null : planTitles(store, root, run);
|
|
85
|
+
const current = run !== null && receipt !== null && family.current.refId === run.taskRef;
|
|
86
|
+
const nextActions = [];
|
|
87
|
+
if (!run)
|
|
88
|
+
nextActions.push('Open the task to inspect its progress.');
|
|
89
|
+
else if (!current)
|
|
90
|
+
nextActions.push('Open the current result before completing or requesting a revision.');
|
|
91
|
+
else {
|
|
92
|
+
if (assignment?.state === 'ready-to-check')
|
|
93
|
+
nextActions.push(`Complete: toolroll task complete ${safe(family.root.id)} --digest ${receipt.digest}`);
|
|
94
|
+
const diff = artifacts.find(one => one.kind === 'terminal-diff');
|
|
95
|
+
if (run.role === 'builder' && diff && readVerifiedArtifact(root, diff).ok) {
|
|
96
|
+
nextActions.push(`Revise: toolroll task revise ${safe(family.root.id)} --run ${run.id} --feedback "requested change"`);
|
|
97
|
+
}
|
|
98
|
+
else if (run.role === 'builder')
|
|
99
|
+
nextActions.push('Revision unavailable: saved changes are not recorded or cannot be read.');
|
|
100
|
+
if (assignment?.state === 'ready-to-check' || assignment?.state === 'complete') {
|
|
101
|
+
if (run.role === 'builder' && run.outcome === 'built')
|
|
102
|
+
nextActions.push(assignment.state === 'complete'
|
|
103
|
+
? 'Merge into a release through the normal release approval and check process.'
|
|
104
|
+
: 'After completion, merge into a release through the normal release approval and check process.');
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
nextActions.push(`Open the task: ${safe(assignment?.detail)}`);
|
|
108
|
+
}
|
|
109
|
+
return { ok: true, brief: {
|
|
110
|
+
header: { task: safe(run === null ? task : store.externalIdFor(run.taskRef)), run: run?.id ?? MISSING,
|
|
111
|
+
provider: safe(run?.provider), model: safe(run?.model), minutes: Number.isFinite(duration) && duration >= 0 ? Math.round(duration * 10) / 10 : MISSING,
|
|
112
|
+
outcome: safe(run?.outcome), base: sha(run?.baseRevision), head: sha(run?.headRevision), state: run !== null && !current ? 'Earlier result' : assignment === null ? MISSING : assignmentPresentationOf(assignment).status.label },
|
|
113
|
+
conclusion: safe(conclusion, 600),
|
|
114
|
+
changedFiles: stat === null ? MISSING : { files: [...stat.files].sort((a, b) => ((b.additions ?? 0) + (b.deletions ?? 0)) - ((a.additions ?? 0) + (a.deletions ?? 0)))
|
|
115
|
+
.slice(0, 15).map(one => ({ path: safe(one.path), additions: one.additions ?? MISSING, deletions: one.deletions ?? MISSING })), total: stat.fileCount, omitted: Math.max(0, stat.fileCount - Math.min(15, stat.files.length)) },
|
|
116
|
+
check: { status: status === 'unavailable' ? MISSING : status, exit: exit ?? MISSING, failed: status !== 'failed' ? [] : failed.length ? failed : MISSING },
|
|
117
|
+
review: review === null ? MISSING : { state: review.state, reason: safe(review.reason), findings: shown, counts, omitted },
|
|
118
|
+
screenshots: screenshots.length ? { files: screenshots.slice(0, 8), omitted: Math.max(0, screenshots.length - 8) } : MISSING,
|
|
119
|
+
plan: plan === null || plan.length === 0 ? MISSING : { titles: plan.slice(0, 5).map(one => safe(one)), omitted: Math.max(0, plan.length - 5) },
|
|
120
|
+
nextActions,
|
|
121
|
+
} };
|
|
122
|
+
}
|
|
123
|
+
function planTitles(store, root, run) {
|
|
124
|
+
const revision = run.planRevision == null ? null : store.getPlanRevision(run.planRevision);
|
|
125
|
+
// Historical runs cannot borrow a plan written after they started.
|
|
126
|
+
const artifact = revision === null ? store.runsFor(run.taskRef).filter(one => one.role === 'planner' && one.id < run.id)
|
|
127
|
+
.sort((a, b) => b.id - a.id).flatMap(one => store.artifactsFor(one.id)).find(one => one.kind === 'plan') : store.getArtifact(revision.artifact);
|
|
128
|
+
const text = verifiedText(root, artifact);
|
|
129
|
+
if (text === null)
|
|
130
|
+
return null;
|
|
131
|
+
const parsed = parseExecutionPlanDocument(text);
|
|
132
|
+
return parsed.ok ? parsed.document.milestones : text.split(/\r?\n/).filter(line => /^#{1,6}\s+\S/.test(line)).map(line => line.replace(/^#{1,6}\s+/, ''));
|
|
133
|
+
}
|
|
134
|
+
/** Text and JSON share this bounded projection; --all includes LOW details within the same line budget. */
|
|
135
|
+
export function renderReviewBrief(brief) {
|
|
136
|
+
const h = brief.header;
|
|
137
|
+
const lines = [`${h.task} · run ${h.run} · ${h.provider}/${h.model} · ${h.minutes} min · ${h.outcome} · ${h.base} → ${h.head} · ${h.state}`,
|
|
138
|
+
`Conclusion: ${brief.conclusion}`, 'Changed files:'];
|
|
139
|
+
if (brief.changedFiles === MISSING)
|
|
140
|
+
lines.push(` ${MISSING}`);
|
|
141
|
+
else {
|
|
142
|
+
lines.push(...brief.changedFiles.files.map(one => ` ${one.path} +${one.additions} / -${one.deletions}`));
|
|
143
|
+
if (brief.changedFiles.total === 0)
|
|
144
|
+
lines.push(' none');
|
|
145
|
+
if (brief.changedFiles.omitted)
|
|
146
|
+
lines.push(` and ${brief.changedFiles.omitted} more`);
|
|
147
|
+
}
|
|
148
|
+
const check = brief.check;
|
|
149
|
+
lines.push(`Check: ${check.status} · exit ${check.exit}${check.failed === MISSING ? ` · failed parts: ${MISSING}` : check.failed.length ? ` · failed: ${check.failed.join('; ')}` : ''}`);
|
|
150
|
+
if (brief.review === MISSING)
|
|
151
|
+
lines.push(`Automatic review: ${MISSING}`);
|
|
152
|
+
else {
|
|
153
|
+
lines.push(`Automatic review: ${brief.review.state}${brief.review.reason === MISSING ? '' : ` · ${brief.review.reason}`}`);
|
|
154
|
+
lines.push(...brief.review.findings.map(one => ` ${one.severity} ${one.file}:${one.line} — ${one.scenario}`));
|
|
155
|
+
for (const severity of FINDING_SEVERITIES)
|
|
156
|
+
if (brief.review.omitted[severity])
|
|
157
|
+
lines.push(` ${severity}: ${brief.review.omitted[severity]} more (${brief.review.counts[severity]} total)`);
|
|
158
|
+
if (Object.values(brief.review.counts).every(count => count === 0) && brief.review.state === 'reviewed')
|
|
159
|
+
lines.push(' no findings');
|
|
160
|
+
}
|
|
161
|
+
lines.push('Screenshots:');
|
|
162
|
+
if (brief.screenshots === MISSING)
|
|
163
|
+
lines.push(` ${MISSING}`);
|
|
164
|
+
else {
|
|
165
|
+
lines.push(...brief.screenshots.files.map(one => ` ${one.path} (${one.bytes}${typeof one.bytes === 'number' ? ' bytes' : ''})`));
|
|
166
|
+
if (brief.screenshots.omitted)
|
|
167
|
+
lines.push(` and ${brief.screenshots.omitted} more`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('Plan:');
|
|
170
|
+
if (brief.plan === MISSING)
|
|
171
|
+
lines.push(` ${MISSING}`);
|
|
172
|
+
else {
|
|
173
|
+
lines.push(...brief.plan.titles.map(one => ` ${one}`));
|
|
174
|
+
if (brief.plan.omitted)
|
|
175
|
+
lines.push(` and ${brief.plan.omitted} more`);
|
|
176
|
+
}
|
|
177
|
+
lines.push('Next:', ...brief.nextActions.map(one => ` ${one}`));
|
|
178
|
+
return lines;
|
|
179
|
+
}
|
package/dist/task-sizing.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type SizeAnswer } from "./contracts/task-sizing.js";
|
|
1
2
|
import { run, type ExecResult } from "./exec.js";
|
|
2
|
-
import { type
|
|
3
|
+
import { type TaskSizing } from "./phase-routing.js";
|
|
3
4
|
import type { Store } from "./store.js";
|
|
4
5
|
/** The classifier's whole budget, from asking to answer. */
|
|
5
6
|
export declare const SIZING_BUDGET_MS = 5000;
|
|
@@ -11,11 +12,7 @@ export type SizingInput = {
|
|
|
11
12
|
outOfScope?: string | null | undefined;
|
|
12
13
|
touches?: readonly string[] | undefined;
|
|
13
14
|
};
|
|
14
|
-
export type SizeAnswer
|
|
15
|
-
size: TaskSize;
|
|
16
|
-
risky: boolean;
|
|
17
|
-
reason: string;
|
|
18
|
-
};
|
|
15
|
+
export type { SizeAnswer } from "./contracts/task-sizing.js";
|
|
19
16
|
/** One classifier: null (or a throw) is "no answer"; the signal ends at the budget. */
|
|
20
17
|
export type Sizer = (input: SizingInput, signal: AbortSignal) => Promise<SizeAnswer | null>;
|
|
21
18
|
/** Structural or broad work — the description signal planning has always read. */
|
|
@@ -27,26 +24,6 @@ export declare function planningSignals(input: SizingInput): boolean;
|
|
|
27
24
|
export declare function heuristicSizing(input: SizingInput, why?: string | null): TaskSizing;
|
|
28
25
|
/** What the classifier reads: the task as data, bounded, keys blanked. */
|
|
29
26
|
export declare function sizingText(input: SizingInput): string;
|
|
30
|
-
export declare const SIZING_SCHEMA: {
|
|
31
|
-
readonly type: "object";
|
|
32
|
-
readonly properties: {
|
|
33
|
-
readonly size: {
|
|
34
|
-
readonly type: "string";
|
|
35
|
-
readonly enum: readonly ["small", "medium", "large"];
|
|
36
|
-
};
|
|
37
|
-
readonly risky: {
|
|
38
|
-
readonly type: "boolean";
|
|
39
|
-
};
|
|
40
|
-
readonly reason: {
|
|
41
|
-
readonly type: "string";
|
|
42
|
-
readonly maxLength: 160;
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
readonly required: readonly ["size", "risky", "reason"];
|
|
46
|
-
readonly additionalProperties: false;
|
|
47
|
-
};
|
|
48
|
-
/** Read an answer back: only the three sizes, a boolean, and a short reason. */
|
|
49
|
-
export declare function readSizeAnswer(value: unknown): SizeAnswer | null;
|
|
50
27
|
type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
|
|
51
28
|
/** A small Claude model on this computer's sign-in: no tools, no MCP servers, no repository, credential keys stripped.
|
|
52
29
|
* The abort ends it: a late answer never keeps a command (or its process) waiting. */
|
|
@@ -75,4 +52,3 @@ export declare function filingSizerInstalled(): boolean;
|
|
|
75
52
|
export declare function refineFiledSizing(store: Store, taskId: string, input: SizingInput, followPlanning: boolean, clock?: () => Date): Promise<void> | null;
|
|
76
53
|
/** Wait for every classification still in flight (each is bounded by the budget). */
|
|
77
54
|
export declare function settleSizings(): Promise<void>;
|
|
78
|
-
export {};
|
package/dist/task-sizing.js
CHANGED
|
@@ -20,12 +20,13 @@ import { randomUUID } from "node:crypto";
|
|
|
20
20
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
import { join } from "node:path";
|
|
23
|
+
import { readSizeAnswer, SIZING_MODEL_SCHEMA } from "./contracts/task-sizing.js";
|
|
23
24
|
import { strictJsonParse } from "./converse.js";
|
|
24
25
|
import { redactSecretLines, scanForSecrets } from "./evidence.js";
|
|
25
26
|
import { run, terminateOwnedProcesses } from "./exec.js";
|
|
26
27
|
import { JEV_MODEL, JEV_URL } from "./flow-sort.js";
|
|
27
28
|
import { readProviderKey } from "./keys.js";
|
|
28
|
-
import {
|
|
29
|
+
import {} from "./phase-routing.js";
|
|
29
30
|
import { ALL_CREDENTIAL_ENV } from "./provider.js";
|
|
30
31
|
/** The classifier's whole budget, from asking to answer. */
|
|
31
32
|
export const SIZING_BUDGET_MS = 5_000;
|
|
@@ -49,8 +50,11 @@ export function heuristicSizing(input, why = null) {
|
|
|
49
50
|
const clip = (text, cap) => (text.length <= cap ? text : `${text.slice(0, cap - 1)}…`);
|
|
50
51
|
/** Key-shaped lines never leave the machine. */
|
|
51
52
|
const blank = (text) => redactSecretLines(text, scanForSecrets(text));
|
|
52
|
-
/**
|
|
53
|
-
|
|
53
|
+
/** An answer read through the contract (src/contracts/task-sizing.ts), its reason on one line; null when it isn't one. */
|
|
54
|
+
function answerOf(value) {
|
|
55
|
+
const read = readSizeAnswer(value);
|
|
56
|
+
return read.ok ? read.value : null;
|
|
57
|
+
}
|
|
54
58
|
const SIZE_MEANS = {
|
|
55
59
|
small: "a contained change a fast model can make directly: copy, a style, a config value, one small function, one or two files",
|
|
56
60
|
medium: "ordinary feature or fix work across a few files that benefits from the everyday model",
|
|
@@ -66,25 +70,6 @@ export function sizingText(input) {
|
|
|
66
70
|
...(input.touches === undefined || input.touches.length === 0 ? [] : [`Paths: ${clip(input.touches.slice(0, 40).join(", "), 1500)}`]),
|
|
67
71
|
].join("\n");
|
|
68
72
|
}
|
|
69
|
-
export const SIZING_SCHEMA = {
|
|
70
|
-
type: "object",
|
|
71
|
-
properties: {
|
|
72
|
-
size: { type: "string", enum: ["small", "medium", "large"] },
|
|
73
|
-
risky: { type: "boolean" },
|
|
74
|
-
reason: { type: "string", maxLength: 160 },
|
|
75
|
-
},
|
|
76
|
-
required: ["size", "risky", "reason"],
|
|
77
|
-
additionalProperties: false,
|
|
78
|
-
};
|
|
79
|
-
/** Read an answer back: only the three sizes, a boolean, and a short reason. */
|
|
80
|
-
export function readSizeAnswer(value) {
|
|
81
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
82
|
-
return null;
|
|
83
|
-
const v = value;
|
|
84
|
-
if (!isTaskSize(v["size"]) || typeof v["risky"] !== "boolean")
|
|
85
|
-
return null;
|
|
86
|
-
return { size: v["size"], risky: v["risky"], reason: typeof v["reason"] === "string" ? oneLine(v["reason"]) : "" };
|
|
87
|
-
}
|
|
88
73
|
/** A small Claude model on this computer's sign-in: no tools, no MCP servers, no repository, credential keys stripped.
|
|
89
74
|
* The abort ends it: a late answer never keeps a command (or its process) waiting. */
|
|
90
75
|
export function claudeSizer(runner = run, model = SIZING_CLAUDE_MODEL, budgetMs = SIZING_BUDGET_MS) {
|
|
@@ -108,7 +93,7 @@ export function claudeSizer(runner = run, model = SIZING_CLAUDE_MODEL, budgetMs
|
|
|
108
93
|
sizingText(input),
|
|
109
94
|
].join("\n");
|
|
110
95
|
const result = await runner("claude", [
|
|
111
|
-
"-p", "--output-format", "json", "--json-schema", JSON.stringify(
|
|
96
|
+
"-p", "--output-format", "json", "--json-schema", JSON.stringify(SIZING_MODEL_SCHEMA), "--safe-mode", "--no-session-persistence", "--tools", "", "--permission-mode", "dontAsk",
|
|
112
97
|
"--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--model", model,
|
|
113
98
|
], { cwd: dir, stdin: prompt, timeoutMs: budgetMs, maxBuffer: 256 * 1024, omitEnv: ALL_CREDENTIAL_ENV, processGroup: true, owner, beforeSpawn: () => !signal.aborted });
|
|
114
99
|
if (signal.aborted || result.notFound || result.timedOut || result.code !== 0)
|
|
@@ -117,7 +102,7 @@ export function claudeSizer(runner = run, model = SIZING_CLAUDE_MODEL, budgetMs
|
|
|
117
102
|
const body = parsed.ok && typeof parsed.value === "object" && parsed.value !== null && !Array.isArray(parsed.value) ? parsed.value : null;
|
|
118
103
|
if (body === null || body["is_error"] === true || body["subtype"] !== "success")
|
|
119
104
|
return null;
|
|
120
|
-
return
|
|
105
|
+
return answerOf(body["structured_output"]);
|
|
121
106
|
}
|
|
122
107
|
finally {
|
|
123
108
|
signal.removeEventListener("abort", stop);
|
|
@@ -149,10 +134,10 @@ export function jevSizer(fetcher, key) {
|
|
|
149
134
|
const answers = (body["answers"] ?? {});
|
|
150
135
|
const size = answers["size"]?.["choice"];
|
|
151
136
|
const risky = answers["risky"]?.["noul"];
|
|
152
|
-
if (
|
|
137
|
+
if (typeof risky !== "number" || !Number.isFinite(risky))
|
|
153
138
|
return null;
|
|
154
139
|
const sure = answers["size"]?.["confidence"];
|
|
155
|
-
return { size, risky: risky >= 0.5, reason: typeof sure === "number" && Number.isFinite(sure) ? `Jev is ${Math.round(Math.min(1, Math.max(0, sure)) * 100)}% sure` : "" };
|
|
140
|
+
return answerOf({ size, risky: risky >= 0.5, reason: typeof sure === "number" && Number.isFinite(sure) ? `Jev is ${Math.round(Math.min(1, Math.max(0, sure)) * 100)}% sure` : "" });
|
|
156
141
|
};
|
|
157
142
|
}
|
|
158
143
|
/** The owner's own classifier: Jev when an OpenRouter key is set up, otherwise Claude on this computer's sign-in. */
|
package/dist/task-text.d.ts
CHANGED
|
@@ -3,11 +3,6 @@ export declare const TASK_TEXT_LIMITS: {
|
|
|
3
3
|
readonly text: 8000;
|
|
4
4
|
readonly textBytes: 32000;
|
|
5
5
|
};
|
|
6
|
-
export declare const TASK_SCOPE_TEXT_SCHEMA: {
|
|
7
|
-
readonly type: "string";
|
|
8
|
-
readonly maxLength: 8000;
|
|
9
|
-
readonly description: `At most ${string} characters (UTF-16 code units) and ${string} UTF-8 bytes; no control or disguised text. Longer text is refused with its length, not cut: shorten it and call again.`;
|
|
10
|
-
};
|
|
11
6
|
export type TaskTextRefusal = {
|
|
12
7
|
ok: false;
|
|
13
8
|
reason: "bad-title" | "bad-goal";
|
package/dist/task-text.js
CHANGED
|
@@ -6,10 +6,8 @@
|
|
|
6
6
|
* runtime with lower limits. */
|
|
7
7
|
import { hasForbiddenControls, hasDisguisedText } from "./decision.js";
|
|
8
8
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
9
|
-
export const TASK_TEXT_LIMITS = { title:
|
|
9
|
+
export const TASK_TEXT_LIMITS = { title: TEXT_LIMITS.taskTitle, text: TEXT_LIMITS.goal, textBytes: TEXT_LIMITS.goalBytes };
|
|
10
10
|
const n = (value) => value.toLocaleString("en-US");
|
|
11
|
-
export const TASK_SCOPE_TEXT_SCHEMA = { type: "string", maxLength: TASK_TEXT_LIMITS.text,
|
|
12
|
-
description: `At most ${n(TASK_TEXT_LIMITS.text)} characters (UTF-16 code units) and ${n(TASK_TEXT_LIMITS.textBytes)} UTF-8 bytes; no control or disguised text. Longer text is refused with its length, not cut: shorten it and call again.` };
|
|
13
11
|
const dishonest = (text) => hasForbiddenControls(text) || hasDisguisedText(text);
|
|
14
12
|
const overBytes = (text, cap) => Buffer.byteLength(text, "utf8") > cap;
|
|
15
13
|
const BYTE_CAPS = { title: 800, text: TASK_TEXT_LIMITS.textBytes, path: 800 };
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* the person it was asked of. Answered anywhere, every button retires.
|
|
12
12
|
*/
|
|
13
13
|
import { randomBytes } from "node:crypto";
|
|
14
|
+
import { partContent, savedChatPart } from "./chat-delivery-state.js";
|
|
14
15
|
import { answerTeammateQuestion } from "./teammate-work.js";
|
|
15
16
|
import { labelOf } from "./teammate-admin.js";
|
|
17
|
+
import { telegramButton } from "./contracts/telegram-callback.js";
|
|
16
18
|
/** The key notifyPeople gives a teammate's question: flow-card:<card>:teammate-q:<question>:<person>. */
|
|
17
19
|
export const TEAMMATE_Q_KEY = /^flow-card:[1-9][0-9]{0,14}:teammate-q:([1-9][0-9]{0,14}):/;
|
|
18
20
|
export const IN_WORDS = "Answer in words";
|
|
@@ -41,8 +43,8 @@ export function telegramQuestionButtons(store, binding, question, now) {
|
|
|
41
43
|
const rows = [];
|
|
42
44
|
const options = choices.filter(one => one.choice !== null);
|
|
43
45
|
for (let at = 0; at < options.length; at += 2)
|
|
44
|
-
rows.push(options.slice(at, at + 2).map(one => (
|
|
45
|
-
rows.push([
|
|
46
|
+
rows.push(options.slice(at, at + 2).map(one => telegramButton(one.label.slice(0, 60), one.token)));
|
|
47
|
+
rows.push([telegramButton(choices.at(-1).label, choices.at(-1).token)]);
|
|
46
48
|
return { keyboard: rows, tokens: choices.map(one => one.token) };
|
|
47
49
|
}
|
|
48
50
|
/** A tapped question button, inside the update's transaction. */
|
|
@@ -90,7 +92,7 @@ export function questionParts(store, notification, binding) {
|
|
|
90
92
|
/** The live buttons on one part, in the order they were minted. */
|
|
91
93
|
export function chatQuestionButtons(state, part, now) {
|
|
92
94
|
const row = state.prepare("SELECT payload FROM chat_part WHERE id=?").get(part);
|
|
93
|
-
const content = row === undefined ? null :
|
|
95
|
+
const content = row === undefined ? null : partContent(String(row["payload"]));
|
|
94
96
|
const labels = new Map((content?.question?.choices ?? []).map(one => [one.choice ?? "", one.label]));
|
|
95
97
|
return state.prepare("SELECT token,choice FROM chat_question_action WHERE part=? AND consumed IS NULL AND expires>? ORDER BY rowid").all(part, now.toISOString())
|
|
96
98
|
.map(one => ({ token: String(one.token), label: (labels.get(one.choice ?? "") ?? IN_WORDS).slice(0, 75), words: one.choice === null }));
|
|
@@ -98,9 +100,9 @@ export function chatQuestionButtons(state, part, now) {
|
|
|
98
100
|
/** Show the tapped notice again with the answer, and without its buttons. */
|
|
99
101
|
function repaint(state, part, event, line) {
|
|
100
102
|
const row = state.prepare("SELECT payload FROM chat_part WHERE id=?").get(part);
|
|
101
|
-
const before = row === undefined ? { text: "" } :
|
|
103
|
+
const before = row === undefined ? { text: "" } : partContent(String(row["payload"]));
|
|
102
104
|
const content = { text: `${before.text}\n\n${line}`.slice(0, 3400), edit: event.ts, ...(before.link === undefined ? {} : { link: before.link }), ...(before.channel === undefined ? {} : { channel: before.channel }) };
|
|
103
|
-
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE id=?").run(
|
|
105
|
+
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL WHERE id=?").run(savedChatPart(content), part);
|
|
104
106
|
}
|
|
105
107
|
/** A tapped question button, inside the action's transaction; false when the token isn't one. */
|
|
106
108
|
export function applyChatQuestionTap(options, event, binding, token, now) {
|
package/dist/teammate-work.js
CHANGED
|
@@ -111,9 +111,10 @@ export async function teammateTurn(store, flow, definition, stage, card, mate, n
|
|
|
111
111
|
store.addTeammateTurn({ teammate: mate.id, card: card.id, model, ok: reply.ok, ms: reply.ok ? reply.ms : Date.now() - asked, costUsd: reply.costUsd ?? null, tokensIn: reply.tokensIn ?? null, tokensOut: reply.tokensOut ?? null }, now);
|
|
112
112
|
if (!reply.ok)
|
|
113
113
|
return { state: "retry", said: reply.said, ...(log.length === 0 ? {} : { log: log.join("\n\n") }) };
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
return { state: "retry", said: `${context.name}'s answer wasn't one this zone allows.`, log: [...log, JSON.stringify(reply.value).slice(0, 4000)].join("\n\n") };
|
|
114
|
+
const read = readTurn(reply.value, context);
|
|
115
|
+
if (!read.ok)
|
|
116
|
+
return { state: "retry", said: `${context.name}'s answer wasn't one this zone allows.`, log: [...log, read.issues.map(issue => issue.line).join("\n"), JSON.stringify(reply.value).slice(0, 4000)].join("\n\n") };
|
|
117
|
+
const answer = read.value;
|
|
117
118
|
const header = `${context.name} (${model}) · ${(reply.ms / 1000).toFixed(1)} s`;
|
|
118
119
|
const over = turnOverruns(answer);
|
|
119
120
|
if (over.length > 0 && !shortened) {
|
|
@@ -243,8 +244,11 @@ function carryOut(store, flow, definition, stage, card, mate, context, answer, n
|
|
|
243
244
|
// route: the answer names where the card goes; its text is what the next zones use.
|
|
244
245
|
const picked = handleAnswers(stage).find(one => one.answer.toLowerCase() === answer.answer.toLowerCase());
|
|
245
246
|
// What the next zones read: whole up to what a step passes on; longer is attached whole to the card, and they read a link to it.
|
|
246
|
-
if (answer.text !== "")
|
|
247
|
-
|
|
247
|
+
if (answer.text !== "") {
|
|
248
|
+
const whole = wholeDraft(answer.text);
|
|
249
|
+
const text = keptWhole(store, flow, card, actor, whole, TEXT_LIMITS.stageOutput, "a step passes on", `What ${label} wrote for the next zones`, now);
|
|
250
|
+
store.updateFlowCard(card.id, { outputs: { ...card.outputs, [stage.id]: text }, ...(text === whole ? {} : { attached: { [stage.id]: whole } }) }, now);
|
|
251
|
+
}
|
|
248
252
|
const said = `Sent “${card.title}” to ${titleOf(picked.to)}${picked.answer === CARRY_ON ? "" : ` (${picked.answer})`}${answer.reason === "" ? "" : `: ${answer.reason}`}`;
|
|
249
253
|
event("handled", said);
|
|
250
254
|
// v96: a zone that answers whoever asked sends what it wrote back to them, under its name.
|
package/dist/teammates.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { run, type ExecResult } from "./exec.js";
|
|
2
|
+
import type { ContractResult } from "./contracts/contract.js";
|
|
3
|
+
import { type TurnAnswer } from "./contracts/teammate-turn.js";
|
|
2
4
|
import { type Overrun } from "./text-limits.js";
|
|
3
5
|
export declare const SOUL_CHARS = 12000;
|
|
4
6
|
export type Soul = {
|
|
@@ -29,25 +31,11 @@ export declare const TEAMMATE_TEMPLATES: readonly {
|
|
|
29
31
|
about: string;
|
|
30
32
|
soul: string;
|
|
31
33
|
}[];
|
|
32
|
-
|
|
33
|
-
export type TurnAction = "approve" | "send_back" | "hand_off" | "route" | "ask" | "cant" | "use_tool";
|
|
34
|
-
/** v94: "use_tool" asks for one tool call (`tool`: its name, `input`: a JSON object as text); the turn goes on with its answer. */
|
|
35
|
-
export type TurnAnswer = {
|
|
36
|
-
action: TurnAction;
|
|
37
|
-
answer: string;
|
|
38
|
-
text: string;
|
|
39
|
-
note: string;
|
|
40
|
-
question: string;
|
|
41
|
-
options: string[];
|
|
42
|
-
reason: string;
|
|
43
|
-
tool: string;
|
|
44
|
-
input: string;
|
|
45
|
-
/** v95: one short fact worth keeping for later cards ("": none). */
|
|
46
|
-
remember: string;
|
|
47
|
-
};
|
|
34
|
+
export type { TurnAction, TurnAnswer } from "./contracts/teammate-turn.js";
|
|
48
35
|
/** Each field's limit, in characters: stated in the prompt before it answers (turnPrompt), and an answer over one is
|
|
49
36
|
* asked once to shorten (teammateTurn); one still over is kept whole, never cut. "text" is what the next zones read;
|
|
50
|
-
* "note" is a decision or send-back note.
|
|
37
|
+
* "note" is a decision or send-back note. Not in the turn's schema (src/contracts/teammate-turn.ts): the CLI would
|
|
38
|
+
* refuse an answer a few characters over one whole. */
|
|
51
39
|
export declare const TURN_LIMITS: {
|
|
52
40
|
readonly answer: 60;
|
|
53
41
|
readonly text: 12000;
|
|
@@ -58,49 +46,6 @@ export declare const TURN_LIMITS: {
|
|
|
58
46
|
};
|
|
59
47
|
/** The fields of an answer over their limits. */
|
|
60
48
|
export declare function turnOverruns(answer: TurnAnswer): Overrun[];
|
|
61
|
-
/** The shape Claude answers in: one flat object (a root union is refused). No length limits here: an answer
|
|
62
|
-
* a few characters over one is refused whole by the CLI, so the limits are TURN_LIMITS, checked after. */
|
|
63
|
-
export declare const TURN_SCHEMA: {
|
|
64
|
-
readonly type: "object";
|
|
65
|
-
readonly additionalProperties: false;
|
|
66
|
-
readonly required: readonly ["action", "answer", "text", "note", "question", "options", "reason", "tool", "input", "remember"];
|
|
67
|
-
readonly properties: {
|
|
68
|
-
readonly action: {
|
|
69
|
-
readonly type: "string";
|
|
70
|
-
readonly enum: readonly ["approve", "send_back", "hand_off", "route", "ask", "cant", "use_tool"];
|
|
71
|
-
};
|
|
72
|
-
readonly answer: {
|
|
73
|
-
readonly type: "string";
|
|
74
|
-
};
|
|
75
|
-
readonly text: {
|
|
76
|
-
readonly type: "string";
|
|
77
|
-
};
|
|
78
|
-
readonly note: {
|
|
79
|
-
readonly type: "string";
|
|
80
|
-
};
|
|
81
|
-
readonly question: {
|
|
82
|
-
readonly type: "string";
|
|
83
|
-
};
|
|
84
|
-
readonly options: {
|
|
85
|
-
readonly type: "array";
|
|
86
|
-
readonly items: {
|
|
87
|
-
readonly type: "string";
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
readonly reason: {
|
|
91
|
-
readonly type: "string";
|
|
92
|
-
};
|
|
93
|
-
readonly tool: {
|
|
94
|
-
readonly type: "string";
|
|
95
|
-
};
|
|
96
|
-
readonly input: {
|
|
97
|
-
readonly type: "string";
|
|
98
|
-
};
|
|
99
|
-
readonly remember: {
|
|
100
|
-
readonly type: "string";
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
49
|
/** Everything one turn is told: the zone, the card as data, and what its people said. */
|
|
105
50
|
export type TurnContext = {
|
|
106
51
|
soul: string;
|
|
@@ -165,8 +110,13 @@ export type TurnContext = {
|
|
|
165
110
|
};
|
|
166
111
|
};
|
|
167
112
|
export declare function turnPrompt(context: TurnContext): string;
|
|
168
|
-
/**
|
|
169
|
-
|
|
113
|
+
/**
|
|
114
|
+
* A turn's answer, read through its contract (src/contracts/teammate-turn.ts) and then checked against what this zone
|
|
115
|
+
* allows, each refusal naming its path (`action: "route" isn't one this zone allows`). Text is trimmed and kept whole
|
|
116
|
+
* (a field over its limit is asked to shorten, turnOverruns); blank options are dropped, and an ask offers at most 4,
|
|
117
|
+
* each cut to an answer's length, and a tool name is cut to 140, as they always were.
|
|
118
|
+
*/
|
|
119
|
+
export declare function readTurn(value: unknown, context: Pick<TurnContext, "kind" | "canSendBack" | "answers" | "tools">): ContractResult<TurnAnswer>;
|
|
170
120
|
export type TurnRequest = {
|
|
171
121
|
model: string;
|
|
172
122
|
prompt: string;
|
|
@@ -190,10 +140,9 @@ export type TurnReply = ({
|
|
|
190
140
|
export type TurnRunner = (request: TurnRequest) => Promise<TurnReply>;
|
|
191
141
|
export declare const TURN_TIMEOUT_MS = 180000;
|
|
192
142
|
type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
|
|
193
|
-
/** The production runner: Claude through this computer's sign-in, answering in
|
|
143
|
+
/** The production runner: Claude through this computer's sign-in, answering in the turn schema (TURN_MODEL_SCHEMA), with no tools, no MCP servers and no repository. */
|
|
194
144
|
export declare function claudeTurnRunner(runner?: CommandRunner): TurnRunner;
|
|
195
145
|
/** How a teammate is named to people: "Maya · Support". */
|
|
196
146
|
export declare const teammateLabel: (soul: Pick<Soul, "name" | "role">) => string;
|
|
197
147
|
/** How its acts read in a card's history: "Maya (AI)". */
|
|
198
148
|
export declare const teammateActor: (soul: Pick<Soul, "name">) => string;
|
|
199
|
-
export {};
|