@intentic/sandbox-contract 1.304.0 → 1.306.0
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/README.md +5 -3
- package/dist/contracts/agent.contract.d.ts +195 -41
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +8 -8
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +203 -3
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +30 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +18 -5
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +3 -3
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/{host.contract.d.ts → device.contract.d.ts} +75 -2
- package/dist/contracts/device.contract.d.ts.map +1 -0
- package/dist/contracts/device.contract.js +17 -0
- package/dist/contracts/device.contract.js.map +1 -0
- package/dist/contracts/extensions.contract.d.ts +2 -2
- package/dist/contracts/git.contract.d.ts +24 -0
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/panels.contract.d.ts +1 -1
- package/dist/contracts/personas.contract.d.ts +0 -9
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.js +1 -10
- package/dist/contracts/personas.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +138 -98
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.js +1 -0
- package/dist/contracts/runner.contract.js.map +1 -1
- package/dist/contracts/secrets.contract.js +2 -2
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +23 -3
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +0 -23
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +106 -8
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +11 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/events/agent-events.d.ts +85 -10
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +26 -10
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/agent-words.d.ts +20 -0
- package/dist/events/agent-words.d.ts.map +1 -0
- package/dist/events/agent-words.js +82 -0
- package/dist/events/agent-words.js.map +1 -0
- package/dist/events/{cards.d.ts → requests.d.ts} +18 -18
- package/dist/events/requests.d.ts.map +1 -0
- package/dist/events/{cards.js → requests.js} +26 -26
- package/dist/events/requests.js.map +1 -0
- package/dist/events/resume.d.ts +1 -0
- package/dist/events/resume.d.ts.map +1 -1
- package/dist/events/resume.js +2 -0
- package/dist/events/resume.js.map +1 -1
- package/dist/events/system-events.d.ts +19 -3
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +165 -25
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +34 -15
- package/dist/events/transcript.js.map +1 -1
- package/dist/events/watch-wake.d.ts.map +1 -1
- package/dist/events/watch-wake.js +4 -0
- package/dist/events/watch-wake.js.map +1 -1
- package/dist/index.d.ts +581 -99
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/models/model-offer.d.ts +2 -1
- package/dist/models/model-offer.d.ts.map +1 -1
- package/dist/models/model-offer.js +5 -4
- package/dist/models/model-offer.js.map +1 -1
- package/dist/models/model-roles.d.ts +2 -9
- package/dist/models/model-roles.d.ts.map +1 -1
- package/dist/models/model-roles.js +2 -9
- package/dist/models/model-roles.js.map +1 -1
- package/dist/policy/command-classes.d.ts +1 -0
- package/dist/policy/command-classes.d.ts.map +1 -1
- package/dist/policy/command-classes.js +8 -0
- package/dist/policy/command-classes.js.map +1 -1
- package/dist/policy/request-status.d.ts +5 -0
- package/dist/policy/request-status.d.ts.map +1 -0
- package/dist/policy/{card-status.js → request-status.js} +3 -3
- package/dist/policy/request-status.js.map +1 -0
- package/dist/policy/safety-policy.d.ts +1 -1
- package/dist/policy/safety-policy.d.ts.map +1 -1
- package/dist/policy/safety-policy.js +1 -0
- package/dist/policy/safety-policy.js.map +1 -1
- package/dist/policy/turned-away.d.ts +2 -0
- package/dist/policy/turned-away.d.ts.map +1 -0
- package/dist/policy/turned-away.js +13 -0
- package/dist/policy/turned-away.js.map +1 -0
- package/dist/protocol/peer-dial.d.ts +9 -0
- package/dist/protocol/peer-dial.d.ts.map +1 -1
- package/dist/protocol/peer-dial.js +12 -4
- package/dist/protocol/peer-dial.js.map +1 -1
- package/dist/protocol/peer-mcp-server.d.ts +1 -0
- package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
- package/dist/protocol/peer-mcp-server.js +14 -1
- package/dist/protocol/peer-mcp-server.js.map +1 -1
- package/dist/schemas/agent.d.ts +2 -1
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +7 -0
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +26 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +14 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +8 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +25 -12
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +11 -6
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/chat-route.d.ts +49 -0
- package/dist/schemas/chat-route.d.ts.map +1 -0
- package/dist/schemas/{model-route.js → chat-route.js} +17 -5
- package/dist/schemas/chat-route.js.map +1 -0
- package/dist/schemas/context-trim.d.ts +9 -0
- package/dist/schemas/context-trim.d.ts.map +1 -0
- package/dist/schemas/context-trim.js +18 -0
- package/dist/schemas/context-trim.js.map +1 -0
- package/dist/schemas/devices.d.ts +21 -8
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +8 -9
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +4 -4
- package/dist/schemas/extension-updates.js +1 -1
- package/dist/schemas/extension-updates.js.map +1 -1
- package/dist/schemas/git/git.d.ts +83 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +40 -0
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/hosts.d.ts +36 -5
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +7 -5
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/metrics.d.ts +108 -0
- package/dist/schemas/metrics.d.ts.map +1 -0
- package/dist/schemas/metrics.js +77 -0
- package/dist/schemas/metrics.js.map +1 -0
- package/dist/schemas/panels.d.ts +2 -2
- package/dist/schemas/panels.js +1 -1
- package/dist/schemas/panels.js.map +1 -1
- package/dist/schemas/personas.d.ts +1 -13
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +3 -20
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +1 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +6 -0
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/secrets.d.ts +1 -1
- package/dist/schemas/secrets.d.ts.map +1 -1
- package/dist/schemas/secrets.js +1 -1
- package/dist/schemas/secrets.js.map +1 -1
- package/dist/schemas/settings.d.ts +0 -42
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +1 -16
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/shared.d.ts +2 -2
- package/dist/schemas/shared.d.ts.map +1 -1
- package/dist/schemas/shared.js +3 -3
- package/dist/schemas/shared.js.map +1 -1
- package/dist/schemas/system-prompt.d.ts +104 -0
- package/dist/schemas/system-prompt.d.ts.map +1 -0
- package/dist/schemas/system-prompt.js +25 -0
- package/dist/schemas/system-prompt.js.map +1 -0
- package/dist/schemas/workflows.d.ts +2 -2
- package/dist/schemas/workflows.d.ts.map +1 -1
- package/dist/schemas/workflows.js +2 -2
- package/dist/schemas/workflows.js.map +1 -1
- package/dist/state/definition.d.ts +18 -10
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/fix-attempt-plan.d.ts +4 -0
- package/dist/state/fix-attempt-plan.d.ts.map +1 -1
- package/dist/state/fix-attempt-plan.js +3 -2
- package/dist/state/fix-attempt-plan.js.map +1 -1
- package/dist/state/fix-stance.d.ts +1 -0
- package/dist/state/fix-stance.d.ts.map +1 -1
- package/dist/state/fix-stance.js +11 -0
- package/dist/state/fix-stance.js.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +8 -1
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/runtime-state.d.ts +0 -1
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +0 -1
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +4 -4
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +4 -4
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/documents.d.ts +2 -2
- package/dist/text/documents.d.ts.map +1 -1
- package/dist/text/documents.js.map +1 -1
- package/dist/text/mentions.d.ts +1 -0
- package/dist/text/mentions.d.ts.map +1 -1
- package/dist/text/mentions.js +5 -1
- package/dist/text/mentions.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +8 -3
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +83 -28
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +9 -9
- package/src/chores/chores.test.ts +1 -1
- package/src/chores/digest.test.ts +1 -1
- package/src/chores/probes.test.ts +5 -2
- package/src/chores/stack.test.ts +1 -1
- package/src/chores/verdict.test.ts +6 -5
- package/src/contracts/agent.contract.ts +10 -9
- package/src/contracts/agents.contract.ts +35 -2
- package/src/contracts/capabilities.contract.ts +3 -3
- package/src/contracts/{host.contract.ts → device.contract.ts} +8 -6
- package/src/contracts/personas.contract.ts +1 -13
- package/src/contracts/runner.contract.ts +3 -2
- package/src/contracts/secrets.contract.ts +2 -2
- package/src/contracts/system.contract.ts +13 -2
- package/src/contracts/webext.contract.ts +1 -1
- package/src/events/agent-events.ts +37 -14
- package/src/events/agent-words.test.ts +68 -0
- package/src/events/agent-words.ts +124 -0
- package/src/events/{cards.ts → requests.ts} +44 -44
- package/src/events/resume.test.ts +8 -1
- package/src/events/resume.ts +4 -0
- package/src/events/transcript.ts +51 -22
- package/src/events/watch-wake.test.ts +3 -2
- package/src/events/watch-wake.ts +4 -0
- package/src/ids/conversation-ids.test.ts +3 -3
- package/src/ids/hostnames.test.ts +1 -1
- package/src/ids/share-paths.test.ts +1 -1
- package/src/ids/tunnel-ids.test.ts +1 -1
- package/src/index.ts +9 -4
- package/src/models/agent-catalog.test.ts +1 -1
- package/src/models/capability-ledger.test.ts +1 -1
- package/src/models/model-offer.test.ts +9 -2
- package/src/models/model-offer.ts +10 -7
- package/src/models/model-order.test.ts +1 -1
- package/src/models/model-pins.test.ts +1 -1
- package/src/models/model-roles.test.ts +1 -1
- package/src/models/model-roles.ts +6 -12
- package/src/models/plan-pools.test.ts +1 -1
- package/src/models/provider-specs.test.ts +3 -3
- package/src/policy/batch-runs.test.ts +1 -1
- package/src/policy/command-classes.test.ts +37 -2
- package/src/policy/command-classes.ts +16 -0
- package/src/policy/credential-material.test.ts +6 -10
- package/src/policy/fence-paths.test.ts +1 -1
- package/src/policy/overlay-lint.test.ts +1 -1
- package/src/policy/owner-ticket.test.ts +1 -1
- package/src/policy/persona-home.test.ts +1 -1
- package/src/policy/{card-status.ts → request-status.ts} +6 -6
- package/src/policy/safety-policy.test.ts +1 -1
- package/src/policy/safety-policy.ts +1 -0
- package/src/policy/search-globs.test.ts +1 -1
- package/src/policy/turned-away.ts +18 -0
- package/src/protocol/container-requirements.test.ts +1 -1
- package/src/protocol/ingress-contract.test.ts +8 -2
- package/src/protocol/ingress-protocol.test.ts +5 -4
- package/src/protocol/peer-dial.test.ts +111 -41
- package/src/protocol/peer-dial.ts +39 -4
- package/src/protocol/peer-mcp-server.test.ts +37 -7
- package/src/protocol/peer-mcp-server.ts +22 -1
- package/src/protocol/routes.test.ts +4 -2
- package/src/schemas/agent.ts +13 -0
- package/src/schemas/agents.ts +25 -0
- package/src/schemas/areas.test.ts +2 -8
- package/src/schemas/automations.ts +5 -5
- package/src/schemas/capabilities.ts +20 -9
- package/src/schemas/chat-route.ts +61 -0
- package/src/schemas/context-trim.test.ts +38 -0
- package/src/schemas/context-trim.ts +29 -0
- package/src/schemas/dev-rebuild-log.test.ts +1 -1
- package/src/schemas/devices.ts +25 -22
- package/src/schemas/extension-updates.ts +1 -1
- package/src/schemas/git/git.test.ts +11 -0
- package/src/schemas/git/git.ts +66 -0
- package/src/schemas/hosts.ts +15 -8
- package/src/schemas/issues.ts +1 -1
- package/src/schemas/metrics.ts +107 -0
- package/src/schemas/panels.ts +2 -2
- package/src/schemas/personas.ts +5 -28
- package/src/schemas/providers/plan-limits.ts +9 -0
- package/src/schemas/secrets.ts +1 -1
- package/src/schemas/settings.test.ts +1 -1
- package/src/schemas/settings.ts +9 -36
- package/src/schemas/shared.ts +6 -6
- package/src/schemas/system-prompt.ts +41 -0
- package/src/schemas/version-seam.test.ts +1 -1
- package/src/schemas/webext.ts +1 -1
- package/src/schemas/workflows.ts +4 -4
- package/src/state/contract-lock.test.ts +15 -11
- package/src/state/fix-attempt-plan.test.ts +10 -1
- package/src/state/fix-attempt-plan.ts +6 -2
- package/src/state/fix-stance.test.ts +10 -1
- package/src/state/fix-stance.ts +13 -0
- package/src/state/history-state.ts +17 -1
- package/src/state/runtime-state.test.ts +3 -9
- package/src/state/runtime-state.ts +0 -4
- package/src/state/versions.test.ts +1 -1
- package/src/state/workspace-state.test.ts +12 -7
- package/src/state/workspace-state.ts +4 -4
- package/src/text/archives.test.ts +1 -1
- package/src/text/documents.test.ts +1 -1
- package/src/text/documents.ts +2 -2
- package/src/text/embed.test.ts +17 -6
- package/src/text/emoji.test.ts +1 -1
- package/src/text/mentions.test.ts +20 -2
- package/src/text/mentions.ts +10 -1
- package/src/text/path-refs.test.ts +1 -1
- package/src/text/title.test.ts +1 -1
- package/src/text/transcript-fold.test.ts +183 -1
- package/src/text/transcript-fold.ts +146 -39
- package/src/text/whisper.test.ts +1 -1
- package/src/text/workflow-faults.test.ts +1 -1
- package/src/time/zone.test.ts +6 -6
- package/dist/contracts/host.contract.d.ts.map +0 -1
- package/dist/contracts/host.contract.js +0 -16
- package/dist/contracts/host.contract.js.map +0 -1
- package/dist/events/cards.d.ts.map +0 -1
- package/dist/events/cards.js.map +0 -1
- package/dist/policy/card-status.d.ts +0 -5
- package/dist/policy/card-status.d.ts.map +0 -1
- package/dist/policy/card-status.js.map +0 -1
- package/dist/schemas/model-route.d.ts +0 -27
- package/dist/schemas/model-route.d.ts.map +0 -1
- package/dist/schemas/model-route.js.map +0 -1
- package/src/schemas/model-route.ts +0 -43
package/src/events/resume.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const RESUME_NOTES = {
|
|
|
20
20
|
carried: `The model provider's usage allowance ran out while this turn was running, which stopped it, and it has been sent again on a different account of the same provider, in this same session: everything you knew is still here. ${REPEATED}`,
|
|
21
21
|
refused:
|
|
22
22
|
"The model provider refused the previous attempt at this request outright, because its usage allowance was spent: no part of the request below was read or acted on, and nothing has been done towards it. It has been sent again, and starts from the beginning. Where the sandbox has measured earlier work on this branch, it is in the note headed 'Where the work stands'.",
|
|
23
|
+
// Turned away before the model saw a word (a memory hold, a dead credential, a model the plan lacks), with the
|
|
24
|
+
// sandbox keeping the turn: nothing to continue from, so the request starts at its beginning.
|
|
25
|
+
door: "The previous attempt at this request was turned away before any of it reached you: no part of the request below was read or acted on. It has now been sent, and starts from the beginning.",
|
|
23
26
|
// A turn parked on the user when the daemon died; what follows the note is their actual answer, not a repeat.
|
|
24
27
|
answered:
|
|
25
28
|
"The sandbox restarted while this conversation was waiting for the user to respond; it is back, and their response follows below: continue from where the session left off.",
|
|
@@ -52,6 +55,7 @@ const RESUME_DISCLOSURES: Record<ResumeReason, ResumeDisclosure> = {
|
|
|
52
55
|
switched: { kind: "notice", text: "Sent again on the switched account after the allowance ran out mid-turn, in a fresh session." },
|
|
53
56
|
carried: { kind: "notice", text: "Sent again on the switched account after the allowance ran out mid-turn, carrying the session with it." },
|
|
54
57
|
refused: { kind: "notice", text: "Sent again after the allowance refused it: nothing had run." },
|
|
58
|
+
door: { kind: "notice", text: "Sent again: the first attempt was turned away before anything ran." },
|
|
55
59
|
answered: { kind: "note", note: { title: "Picked back up after a sandbox restart", text: RESUME_NOTES.answered } },
|
|
56
60
|
};
|
|
57
61
|
|
package/src/events/transcript.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { z } from "zod";
|
|
|
2
2
|
import { AgentHarnessSchema, AgentProviderSchema } from "../schemas/agent.js";
|
|
3
3
|
import { ShareDetailSchema } from "../schemas/share.js";
|
|
4
4
|
import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
|
|
5
|
-
import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind} from "./
|
|
6
|
-
import {
|
|
5
|
+
import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind} from "./requests.js";
|
|
6
|
+
import { browserHelpRequest, capabilityOfferRequest, CapabilityOutcomeSchema, credentialOfferRequest, CredentialReceiptSchema, paymentOfferRequest, PaymentReceiptSchema, PermissionAskSchema, permissionRequest, planRequest, questionRequest, terminalHelpRequest, TodoItemSchema, ToolCallContentSchema, ToolCallLocationSchema, ToolCallStatusSchema, ToolKindSchema } from "./requests.js";
|
|
7
7
|
|
|
8
8
|
// A conversation as recorded and replayed: the rows, the cards they carry, and the patches that change them while a
|
|
9
9
|
// turn runs. One shape for the live row and the recorded one, since they're the same row.
|
|
10
10
|
|
|
11
11
|
// Transcript cards.
|
|
12
|
-
// Status enums for a parked card, settled by the fold from the reply that released it (
|
|
12
|
+
// Status enums for a parked card, settled by the fold from the reply that released it (request-status.ts), riding the row
|
|
13
13
|
// rather than the reply. `cancelled` means nobody answered, not a decision; `pending` is still parked.
|
|
14
14
|
export const PlanStatusSchema = z.enum(["pending", "approved", "rejected", "cancelled"]);
|
|
15
15
|
export type PlanStatus = z.infer<typeof PlanStatusSchema>;
|
|
@@ -25,10 +25,10 @@ export type OfferStatus = z.infer<typeof OfferStatusSchema>;
|
|
|
25
25
|
export const CapabilityOfferStatusSchema = z.enum(["pending", "connecting", "skipped", "cancelled"]);
|
|
26
26
|
export type CapabilityOfferStatus = z.infer<typeof CapabilityOfferStatusSchema>;
|
|
27
27
|
|
|
28
|
-
export const TranscriptPlanSchema = z.object({ ...
|
|
28
|
+
export const TranscriptPlanSchema = z.object({ ...planRequest, status: PlanStatusSchema.describe("Where the decision stands.") });
|
|
29
29
|
export type TranscriptPlan = z.infer<typeof TranscriptPlanSchema>;
|
|
30
30
|
export const TranscriptQuestionSchema = z.object({
|
|
31
|
-
...
|
|
31
|
+
...questionRequest,
|
|
32
32
|
status: QuestionStatusSchema.describe("Where the answer stands."),
|
|
33
33
|
answers: z
|
|
34
34
|
.record(z.string(), z.array(z.string()))
|
|
@@ -37,26 +37,26 @@ export const TranscriptQuestionSchema = z.object({
|
|
|
37
37
|
});
|
|
38
38
|
export type TranscriptQuestion = z.infer<typeof TranscriptQuestionSchema>;
|
|
39
39
|
// `explain`, the judge's sentence, arrives via PermissionAskSchema at raise time; nothing patches it in after.
|
|
40
|
-
export const TranscriptPermissionSchema = PermissionAskSchema.extend({ ...
|
|
40
|
+
export const TranscriptPermissionSchema = PermissionAskSchema.extend({ ...permissionRequest, status: PermissionStatusSchema.describe("Where the decision stands.") });
|
|
41
41
|
export type TranscriptPermission = z.infer<typeof TranscriptPermissionSchema>;
|
|
42
|
-
export const TranscriptBrowserHelpSchema = z.object({ ...
|
|
42
|
+
export const TranscriptBrowserHelpSchema = z.object({ ...browserHelpRequest, status: HelpStatusSchema.describe("How the hand-over ended.") });
|
|
43
43
|
export type TranscriptBrowserHelp = z.infer<typeof TranscriptBrowserHelpSchema>;
|
|
44
|
-
export const TranscriptTerminalHelpSchema = z.object({ ...
|
|
44
|
+
export const TranscriptTerminalHelpSchema = z.object({ ...terminalHelpRequest, status: HelpStatusSchema.describe("How the hand-over ended.") });
|
|
45
45
|
export type TranscriptTerminalHelp = z.infer<typeof TranscriptTerminalHelpSchema>;
|
|
46
46
|
export const TranscriptCapabilityOfferSchema = z.object({
|
|
47
|
-
...
|
|
47
|
+
...capabilityOfferRequest,
|
|
48
48
|
status: CapabilityOfferStatusSchema.describe("Where the decision stands."),
|
|
49
49
|
outcome: CapabilityOutcomeSchema.optional().describe("How an accepted ask's setup ended (the capability_outcome frame)."),
|
|
50
50
|
});
|
|
51
51
|
export type TranscriptCapabilityOffer = z.infer<typeof TranscriptCapabilityOfferSchema>;
|
|
52
52
|
export const TranscriptPaymentOfferSchema = z.object({
|
|
53
|
-
...
|
|
53
|
+
...paymentOfferRequest,
|
|
54
54
|
status: OfferStatusSchema.describe("Where the decision stands."),
|
|
55
55
|
receipt: PaymentReceiptSchema.optional().describe("How the approved payment ended (the payment_receipt frame)."),
|
|
56
56
|
});
|
|
57
57
|
export type TranscriptPaymentOffer = z.infer<typeof TranscriptPaymentOfferSchema>;
|
|
58
58
|
export const TranscriptCredentialOfferSchema = z.object({
|
|
59
|
-
...
|
|
59
|
+
...credentialOfferRequest,
|
|
60
60
|
status: OfferStatusSchema.describe("Where the decision stands."),
|
|
61
61
|
receipt: CredentialReceiptSchema.optional().describe("Who released it, or that somebody refused (the credential_receipt frame)."),
|
|
62
62
|
});
|
|
@@ -131,9 +131,8 @@ export interface TranscriptTool {
|
|
|
131
131
|
subagent?: TranscriptSubagent | undefined;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
export const WatchOutcomeSchema = z.enum(["met", "timeout", "restart-expired"]);
|
|
134
|
+
// Every ending that wakes a conversation off a condition watch; composing and parsing the wake is watch-wake.ts.
|
|
135
|
+
export const WatchOutcomeSchema = z.enum(["met", "timeout", "restart-expired", "broken"]);
|
|
137
136
|
export type WatchOutcome = z.infer<typeof WatchOutcomeSchema>;
|
|
138
137
|
|
|
139
138
|
// A condition watch waking the conversation, as the row carries it. The wake arrives as an ordinary turn prompt that
|
|
@@ -148,6 +147,25 @@ export const TranscriptWatchWakeSchema = z.object({
|
|
|
148
147
|
});
|
|
149
148
|
export type TranscriptWatchWake = z.infer<typeof TranscriptWatchWakeSchema>;
|
|
150
149
|
|
|
150
|
+
// Another agent's words that reached this conversation as a prompt; the row keeps the prompt and names the sender.
|
|
151
|
+
export const TranscriptAgentWordsSchema = z.object({
|
|
152
|
+
kind: z.enum(["peer", "child"]).describe("Who sent it: another conversation in the workspace, or a child agent this one started."),
|
|
153
|
+
from: z.string().describe("The sending conversation's id."),
|
|
154
|
+
title: z.string().optional().describe("The sender's title, when it had one."),
|
|
155
|
+
failed: z.boolean().optional().describe("A child's report on a turn that failed rather than finished."),
|
|
156
|
+
sent: z.string().describe("The whole prompt the model received, disclosed under the row."),
|
|
157
|
+
});
|
|
158
|
+
export type TranscriptAgentWords = z.infer<typeof TranscriptAgentWordsSchema>;
|
|
159
|
+
|
|
160
|
+
// A command the agent left running past its own call; how it is doing now is live state on the card (AgentSummary.jobs).
|
|
161
|
+
export const TranscriptBackgroundJobSchema = z.object({
|
|
162
|
+
id: z.string().describe("The daemon's handle for the job, which its live state on the conversation's card is keyed by."),
|
|
163
|
+
label: z.string().describe("What the job is, in the agent's own words when it gave any, else its command on one line."),
|
|
164
|
+
command: z.string().describe("The command as the agent wrote it, folded to one line."),
|
|
165
|
+
startedAt: z.number().describe("When it started, in milliseconds."),
|
|
166
|
+
});
|
|
167
|
+
export type TranscriptBackgroundJob = z.infer<typeof TranscriptBackgroundJobSchema>;
|
|
168
|
+
|
|
151
169
|
// One note the daemon put before a user's message: the model reads `text`, the chat draws `title` on a row that opens
|
|
152
170
|
// to it. Shared by the live frame and the restored transcript, so it reads the same either way.
|
|
153
171
|
export const TurnNoteSchema = z.object({
|
|
@@ -226,14 +244,23 @@ export const TranscriptRowSchema = z.object({
|
|
|
226
244
|
.describe(
|
|
227
245
|
"A person wrote this in the agent's voice, with no turn behind it. Marked for the human re-reading the conversation months later, so their own words do not pass as the agent's. The agent itself never sees the mark.",
|
|
228
246
|
),
|
|
229
|
-
// The
|
|
247
|
+
// The follow-up this notice offers, by name; the chat decides what it does, and whether it stands. `sendAnyway` sends
|
|
248
|
+
// the held message; `sandboxMemory` offers that and a raise of the sandbox's memory; `sendAgain` sends a turn the
|
|
249
|
+
// sandbox kept after a refusal no raise would clear.
|
|
230
250
|
noticeAction: z
|
|
231
|
-
.enum(["landHold", "depsInstall", "watchStop"])
|
|
251
|
+
.enum(["landHold", "depsInstall", "watchStop", "sandboxMemory", "sendAnyway", "sendAgain"])
|
|
232
252
|
.optional()
|
|
233
253
|
.describe("A one-press follow-up this notice offers, by name. The chat decides what it does and whether it still applies."),
|
|
254
|
+
// Where a refused turn's words wait decides what the notice's press does: the composer's queue, or the sandbox.
|
|
255
|
+
sandboxHeld: z
|
|
256
|
+
.boolean()
|
|
257
|
+
.optional()
|
|
258
|
+
.describe(
|
|
259
|
+
"The sandbox kept this refused turn whole, its message still above, so the notice's press runs that turn again instead of sending anything from the composer, which never held these words.",
|
|
260
|
+
),
|
|
234
261
|
// An unfinished wait this notice describes, by name; whether it's still running is live state, not stored here.
|
|
235
262
|
noticeWait: z
|
|
236
|
-
.enum(["credentialRenewal", "
|
|
263
|
+
.enum(["credentialRenewal", "chatRoute", "watch"])
|
|
237
264
|
.optional()
|
|
238
265
|
.describe("The wait this notice describes, by name, so a reader can say whether it is still on."),
|
|
239
266
|
// Which one, for a wait whose kind can have several in flight at once; without it two armed watches settle together.
|
|
@@ -250,6 +277,8 @@ export const TranscriptRowSchema = z.object({
|
|
|
250
277
|
capabilityOffer: TranscriptCapabilityOfferSchema.optional().describe("The capability setup this row asked for, the decision, and the outcome."),
|
|
251
278
|
paymentOffer: TranscriptPaymentOfferSchema.optional().describe("The payment this row asked for, the decision, and the receipt."),
|
|
252
279
|
watchWake: TranscriptWatchWakeSchema.optional().describe("The condition watch that woke this conversation, and the prompt it was woken with."),
|
|
280
|
+
agentWords: TranscriptAgentWordsSchema.optional().describe("Another agent's words that reached this conversation, whose they are, and the prompt they came as."),
|
|
281
|
+
backgroundJob: TranscriptBackgroundJobSchema.optional().describe("The background job this row marks the start of."),
|
|
253
282
|
credentialOffer: TranscriptCredentialOfferSchema.optional().describe(
|
|
254
283
|
"The gated credential this row asked to use, who may release it, and who did.",
|
|
255
284
|
),
|
|
@@ -257,7 +286,7 @@ export const TranscriptRowSchema = z.object({
|
|
|
257
286
|
export type TranscriptRow = z.infer<typeof TranscriptRowSchema>;
|
|
258
287
|
|
|
259
288
|
// Every card field, as one list, for readers asking whether a row holds a card at all.
|
|
260
|
-
export const
|
|
289
|
+
export const REQUEST_FIELDS = [
|
|
261
290
|
"plan",
|
|
262
291
|
"question",
|
|
263
292
|
"permission",
|
|
@@ -267,12 +296,12 @@ export const CARD_FIELDS = [
|
|
|
267
296
|
"paymentOffer",
|
|
268
297
|
"credentialOffer",
|
|
269
298
|
] as const;
|
|
270
|
-
export type
|
|
271
|
-
export type
|
|
299
|
+
export type RequestField = (typeof REQUEST_FIELDS)[number];
|
|
300
|
+
export type TranscriptRequests = Pick<TranscriptRow, RequestField>;
|
|
272
301
|
// Whether a row holds a card at all, answered or not.
|
|
273
|
-
export const
|
|
302
|
+
export const holdsRequest = (row: TranscriptRequests): boolean => REQUEST_FIELDS.some((field) => row[field] !== undefined);
|
|
274
303
|
// Whether a row is holding the turn open on a card nobody has answered.
|
|
275
|
-
export const isAwaitingDecision = (row:
|
|
304
|
+
export const isAwaitingDecision = (row: TranscriptRequests): boolean => REQUEST_FIELDS.some((field) => row[field]?.status === "pending");
|
|
276
305
|
|
|
277
306
|
// One change to a run's rows, so a client keeps rows rather than frames. Prose/thinking append to a row; a `tool` card
|
|
278
307
|
// replaces whole by id; everything else replaces its row; `drop` removes a row that opened and never wrote.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
2
|
import type { WatchOutcome } from "./transcript.js";
|
|
3
3
|
import { watchWakeOf, watchWakePrompt, watchWakeRow, type WatchWakeFields } from "./watch-wake.js";
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ const fields = (over: Partial<WatchWakeFields> = {}): WatchWakeFields => ({
|
|
|
13
13
|
...over,
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
const OUTCOMES:
|
|
16
|
+
const OUTCOMES: WatchOutcome[] = ["met", "timeout", "restart-expired", "broken"];
|
|
17
17
|
|
|
18
18
|
describe("watch wake", () => {
|
|
19
19
|
// The composer and the parser are the same piece of knowledge; this is what holds them together when either is
|
|
@@ -52,6 +52,7 @@ describe("watch wake", () => {
|
|
|
52
52
|
const textOf = (outcome: WatchOutcome): string => watchWakeRow(watchWakePrompt(fields({ outcome })))?.text ?? "";
|
|
53
53
|
expect(textOf("timeout")).toBe("CI run 316 on intentic/intentic — the watch gave up after 43m.");
|
|
54
54
|
expect(textOf("restart-expired")).toBe("CI run 316 on intentic/intentic — the watch stopped when the sandbox restarted, after 43m.");
|
|
55
|
+
expect(textOf("broken")).toBe("CI run 316 on intentic/intentic — the watch stopped after 43m: its check can no longer run.");
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
it("ignores a prompt that is not a wake, so any reader can ask without checking first", () => {
|
package/src/events/watch-wake.ts
CHANGED
|
@@ -11,6 +11,7 @@ const OPENINGS: Record<WatchOutcome, string> = {
|
|
|
11
11
|
timeout: "Watch timed out: the deadline passed and the check never exited 0. Decide whether to re-arm it, investigate the check, or report back.",
|
|
12
12
|
"restart-expired":
|
|
13
13
|
"Watch stopped: its deadline passed while the daemon was restarting, so it went unchecked for part of that window. It has just been re-checked once and the condition still does not hold. Decide whether to re-arm it, investigate the check, or report back.",
|
|
14
|
+
broken: "Watch stopped: its check can no longer run at all, so it was disarmed rather than left waiting on a condition it could never see. The reason is in the output below. Decide whether to re-arm it with a check that can run, or report back.",
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
// Labels the prompt writes and the parser reads back. `Watching` and `Elapsed` are load-bearing on both sides; the rest
|
|
@@ -51,6 +52,9 @@ const headline = (outcome: WatchOutcome, note: string, elapsed: string): string
|
|
|
51
52
|
if (outcome === "timeout") {
|
|
52
53
|
return `${note} — the watch gave up after ${elapsed}.`;
|
|
53
54
|
}
|
|
55
|
+
if (outcome === "broken") {
|
|
56
|
+
return `${note} — the watch stopped after ${elapsed}: its check can no longer run.`;
|
|
57
|
+
}
|
|
54
58
|
return `${note} — the watch stopped when the sandbox restarted, after ${elapsed}.`;
|
|
55
59
|
};
|
|
56
60
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { test, expect, afterEach, spyOn, jest } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
CI_FIX_PREFIX,
|
|
4
4
|
ciFixConversationId,
|
|
@@ -15,7 +15,7 @@ import { ConversationIdSchema } from "../schemas/agent.js";
|
|
|
15
15
|
|
|
16
16
|
const mockRandomValues = (values: readonly number[]) => {
|
|
17
17
|
const remaining = [...values];
|
|
18
|
-
const getRandomValues =
|
|
18
|
+
const getRandomValues = spyOn(crypto, "getRandomValues").mockImplementation(<T extends ArrayBufferView | null>(array: T): T => {
|
|
19
19
|
if (!(array instanceof Uint32Array)) {
|
|
20
20
|
throw new TypeError(`Expected a Uint32Array`);
|
|
21
21
|
}
|
|
@@ -29,7 +29,7 @@ const mockRandomValues = (values: readonly number[]) => {
|
|
|
29
29
|
return { getRandomValues, remaining };
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
afterEach(() =>
|
|
32
|
+
afterEach(() => jest.restoreAllMocks());
|
|
33
33
|
|
|
34
34
|
test("every generated id passes the conversation-id guard", () => {
|
|
35
35
|
for (let index = 0; index < 2_000; index += 1) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
2
|
import { SHARE_ID, shareId, shareStem } from "./share-paths.js";
|
|
3
3
|
|
|
4
4
|
/* The id a shared conversation is filed under is two things at once: the readable half of a link somebody pastes. */
|
package/src/index.ts
CHANGED
|
@@ -71,7 +71,7 @@ export { diffContract } from "./contracts/diff.contract.js";
|
|
|
71
71
|
export { gitContract } from "./contracts/git.contract.js";
|
|
72
72
|
export { historyContract } from "./contracts/history.contract.js";
|
|
73
73
|
// Not part of `sandboxContract` below: spoken over a device's WebSocket, with the machine implementing it.
|
|
74
|
-
export {
|
|
74
|
+
export { deviceContract } from "./contracts/device.contract.js";
|
|
75
75
|
// Same inversion, spoken over a browser extension's socket, with the extension implementing it.
|
|
76
76
|
export { webextContract } from "./contracts/webext.contract.js";
|
|
77
77
|
// Same inversion again: spoken over a runner's WebSocket, with the runner implementing it.
|
|
@@ -103,13 +103,15 @@ export { vpnContract } from "./contracts/vpn.contract.js";
|
|
|
103
103
|
export { workflowsContract } from "./contracts/workflows.contract.js";
|
|
104
104
|
export { workspaceContract } from "./contracts/workspace.contract.js";
|
|
105
105
|
export * from "./events/agent-events.js";
|
|
106
|
-
export * from "./events/
|
|
106
|
+
export * from "./events/agent-words.js";
|
|
107
|
+
export * from "./events/requests.js";
|
|
107
108
|
export * from "./events/resume.js";
|
|
108
109
|
export * from "./events/system-events.js";
|
|
109
110
|
export * from "./events/transcript.js";
|
|
110
111
|
export * from "./events/verify-nudge.js";
|
|
111
112
|
export * from "./events/watch-wake.js";
|
|
112
|
-
export * from "./policy/
|
|
113
|
+
export * from "./policy/request-status.js";
|
|
114
|
+
export * from "./policy/turned-away.js";
|
|
113
115
|
export * from "./text/mentions.js";
|
|
114
116
|
export * from "./protocol/sse.js";
|
|
115
117
|
export * from "./protocol/routes.js";
|
|
@@ -163,9 +165,11 @@ export * from "./schemas/agents.js";
|
|
|
163
165
|
export * from "./schemas/approvals.js";
|
|
164
166
|
export * from "./schemas/automations.js";
|
|
165
167
|
export * from "./schemas/capabilities.js";
|
|
168
|
+
export * from "./schemas/chat-route.js";
|
|
166
169
|
export * from "./schemas/ci.js";
|
|
167
170
|
export * from "./schemas/providers/claude-gate.js";
|
|
168
171
|
export * from "./schemas/codebase-health.js";
|
|
172
|
+
export * from "./schemas/context-trim.js";
|
|
169
173
|
export * from "./schemas/devices.js";
|
|
170
174
|
export * from "./schemas/engines.js";
|
|
171
175
|
export * from "./schemas/environment.js";
|
|
@@ -184,7 +188,7 @@ export * from "./schemas/logs.js";
|
|
|
184
188
|
export * from "./schemas/loops.js";
|
|
185
189
|
export * from "./schemas/maintenance.js";
|
|
186
190
|
export * from "./schemas/marketplace.js";
|
|
187
|
-
export * from "./schemas/
|
|
191
|
+
export * from "./schemas/metrics.js";
|
|
188
192
|
export * from "./schemas/panels.js";
|
|
189
193
|
export * from "./schemas/personas.js";
|
|
190
194
|
export * from "./schemas/providers/plan-limits.js";
|
|
@@ -200,6 +204,7 @@ export * from "./schemas/sessions.js";
|
|
|
200
204
|
export * from "./schemas/settings.js";
|
|
201
205
|
export * from "./schemas/share.js";
|
|
202
206
|
export * from "./schemas/shared.js";
|
|
207
|
+
export * from "./schemas/system-prompt.js";
|
|
203
208
|
export * from "./schemas/system.js";
|
|
204
209
|
export * from "./schemas/terminal.js";
|
|
205
210
|
export * from "./schemas/turn-break.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
2
|
import { type ModelOffer, offerLines, parseModelPick, renewsInWords } from "./model-offer.js";
|
|
3
3
|
|
|
4
4
|
/* THE OFFER the Auto judge chooses from, and the reading of what it replies. Pure both ways: no catalog, no account. */
|
|
@@ -15,7 +15,14 @@ const OFFER: ModelOffer = {
|
|
|
15
15
|
],
|
|
16
16
|
accounts: {
|
|
17
17
|
claude: [
|
|
18
|
-
{
|
|
18
|
+
{
|
|
19
|
+
id: "work",
|
|
20
|
+
label: "work@studio",
|
|
21
|
+
windows: [
|
|
22
|
+
{ short: "5h", left: 38, resetsAt: IN_TWO_HOURS },
|
|
23
|
+
{ short: "wk", left: 61 },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
19
26
|
{ id: "personal", windows: [] },
|
|
20
27
|
],
|
|
21
28
|
codex: [],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { modelPinKey } from "./model-pins.js";
|
|
2
2
|
import type { AgentProvider } from "../schemas/agent.js";
|
|
3
|
-
import type { ModelPick } from "../schemas/
|
|
3
|
+
import type { ModelPick } from "../schemas/chat-route.js";
|
|
4
4
|
|
|
5
5
|
// What the Auto judge is allowed to choose from, and how its reply is read back. Pure: the daemon gathers the facts
|
|
6
6
|
// (catalogs, connected accounts, headroom readings) and this turns them into the lines the model sees and the
|
|
@@ -100,19 +100,22 @@ export const offerLines = (offer: ModelOffer, now: number): readonly string[] =>
|
|
|
100
100
|
// Wrapper words a model reaches for anyway: a fence, a bullet, quotes, a trailing period.
|
|
101
101
|
const FENCE_LINE = /^```/u;
|
|
102
102
|
const BULLET_LINE = /^[-*•]\s+/u;
|
|
103
|
-
const FIELD = /^(
|
|
103
|
+
const FIELD = /^([a-z][\w-]*)\s*:\s*(.+)$/iu;
|
|
104
104
|
|
|
105
105
|
const bare = (value: string): string => value.trim().replace(/^[`'"]+/u, ``).replace(/[.`'"]+$/u, ``).trim();
|
|
106
106
|
|
|
107
|
-
// The reply's `key: value` lines
|
|
108
|
-
// meant its first answer, and a later line is commentary on it.
|
|
109
|
-
|
|
107
|
+
// The reply's `key: value` lines for the keys asked about, lowercased, bullets and fences stripped. First wins: a model
|
|
108
|
+
// that answers twice meant its first answer, and a later line is commentary on it. Keys are passed in rather than
|
|
109
|
+
// fixed here, since one reply can carry the answers to several questions (chat-route.ts asks two at once), and an
|
|
110
|
+
// empty map is the honest way to tell a keyed reply from a bare one-word one.
|
|
111
|
+
export const replyFields = (reply: string, keys: readonly string[]): ReadonlyMap<string, string> => {
|
|
112
|
+
const wanted = new Set(keys.map((key) => key.toLowerCase()));
|
|
110
113
|
const fields = new Map<string, string>();
|
|
111
114
|
for (const line of reply.split(`\n`)) {
|
|
112
115
|
const trimmed = line.trim().replace(BULLET_LINE, ``);
|
|
113
116
|
const found = FENCE_LINE.test(trimmed) ? null : FIELD.exec(trimmed);
|
|
114
117
|
const key = found?.[1]?.toLowerCase();
|
|
115
|
-
if (key !== undefined && found?.[2] !== undefined && !fields.has(key)) {
|
|
118
|
+
if (key !== undefined && wanted.has(key) && found?.[2] !== undefined && !fields.has(key)) {
|
|
116
119
|
fields.set(key, bare(found[2]));
|
|
117
120
|
}
|
|
118
121
|
}
|
|
@@ -130,7 +133,7 @@ export interface ParsedPick {
|
|
|
130
133
|
// provider has. An unrecognised effort or account is dropped instead, keeping the model it came with — those are
|
|
131
134
|
// refinements, and the turn's own defaults answer for them correctly.
|
|
132
135
|
export const parseModelPick = (reply: string, offer: ModelOffer): ParsedPick => {
|
|
133
|
-
const fields =
|
|
136
|
+
const fields = replyFields(reply, [`model`, `effort`, `account`]);
|
|
134
137
|
const token = fields.get(`model`) ?? ``;
|
|
135
138
|
const chosen = offer.models.find((model) => modelPinKey(model).toLowerCase() === token.toLowerCase());
|
|
136
139
|
if (chosen === undefined) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
2
|
import { compareModelIds, compareUnrankedModelIds, familyOf, namesThinking, releaseOf, tierRankOf } from "./model-order.js";
|
|
3
3
|
|
|
4
4
|
// The order every provider's catalog is served and browsed in; only Anthropic's catalog arrives ranked, the rest hand
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
2
|
import { MODEL_ROLE_BLOCKS, MODEL_ROLES, type ModelRoleSpec } from "./model-roles.js";
|
|
3
3
|
|
|
4
4
|
// The catalog draws the settings page directly, so its properties (every role in one block) must hold of the table
|
|
@@ -58,19 +58,13 @@ export const MODEL_ROLES = [
|
|
|
58
58
|
icon: "check-square",
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
kind: "helper",
|
|
66
|
-
icon: "users",
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
// Picks one offered `provider:model` line, an effort and an account from a fixed list; a classification, not
|
|
70
|
-
// free text. Prompt carries live account headroom, so it must not be a model whose own allowance it is reading.
|
|
61
|
+
// One reading answering both halves of "what does this chat open on": an offered `provider:model` line with an
|
|
62
|
+
// effort and an account, and a persona id, each a classification over a fixed list rather than free text. The
|
|
63
|
+
// id predates the persona half and stays as it is, since it is what a settings file already carries.
|
|
64
|
+
// Prompt carries live account headroom, so it must not be a model whose own allowance it is reading.
|
|
71
65
|
id: "model-router",
|
|
72
|
-
label: "
|
|
73
|
-
blurb: "Which model reads a new chat's first message and picks the model, effort and account
|
|
66
|
+
label: "New chat routing",
|
|
67
|
+
blurb: "Which model reads a new chat's first message and picks what it opens on: the model, effort and account, and the persona.",
|
|
74
68
|
kind: "helper",
|
|
75
69
|
icon: "sparkles",
|
|
76
70
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
2
|
import { bindingWindow, gatesModel, gatingWindows, scopedWindow, windowLive, windowPeriod } from "./plan-pools.js";
|
|
3
3
|
import type { AccountUsage, UsageWindow } from "../schemas/providers/plan-limits.js";
|
|
4
4
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PROVIDER_BRAND_PATHS } from "@intentic/constants";
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, test, expect } from "bun:test";
|
|
3
3
|
import { accessFor, capabilitiesOf, harnessChoosable, PROVIDER_ACCESS, PROVIDER_VENDOR, PROVIDERS, providerLabel } from "./agent-catalog.js";
|
|
4
4
|
import {
|
|
5
5
|
ACCESS_COST,
|
|
@@ -40,12 +40,12 @@ describe("every provider in the table", () => {
|
|
|
40
40
|
if (spec.auth.kind === "translator") {
|
|
41
41
|
// The CLIProxyAPI's own name for this provider, addressed by the daemon's management API.
|
|
42
42
|
expect(cliProxyIdOf(id), `${id} is routed but names no CLIProxyAPI provider`).toBe(spec.auth.cliProxy);
|
|
43
|
-
expect(KeyedProviderSchema.options, `${id} is routed but missing from KeyedProvider`).toContain(id);
|
|
43
|
+
expect<readonly string[]>(KeyedProviderSchema.options, `${id} is routed but missing from KeyedProvider`).toContain(id);
|
|
44
44
|
expect(Object.keys(TranslatorAccountsSchema.shape), `${id} has no slot in TranslatorAccounts`).toContain(id);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
if (spec.auth.kind === "minted") {
|
|
48
|
-
expect(MINTED_PROVIDERS, `${id} is minted but missing from MINTED_PROVIDERS`).toContain(id);
|
|
48
|
+
expect<readonly string[]>(MINTED_PROVIDERS, `${id} is minted but missing from MINTED_PROVIDERS`).toContain(id);
|
|
49
49
|
// The head of this list is what a login/start naming no variant gets; empty means a dead connect button.
|
|
50
50
|
expect(spec.auth.variants.length, `${id} is minted but offers no estate to sign in to`).toBeGreaterThan(0);
|
|
51
51
|
for (const variant of spec.auth.variants) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STATE_DIR } from "@intentic/constants";
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, it, expect } from "bun:test";
|
|
3
3
|
|
|
4
4
|
// Stands in for any role: what's tested is that the role reaches the turn, not which one it is.
|
|
5
5
|
const ROLE = `maintenance-chore` as const;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
2
|
import { classifyCommand, type CommandContext, type CommandSpan, matchCommand } from "./command-classes.js";
|
|
3
3
|
|
|
4
4
|
// Every call states a locus (now required); these default to `device`, the wider reading, so an assertion made here
|
|
@@ -475,7 +475,7 @@ describe("live", () => {
|
|
|
475
475
|
|
|
476
476
|
test("a command substitution inside an echo is not text", () => {
|
|
477
477
|
expect(live(`echo "$(rm -rf /)"`, "files.destructive")).toBe(true);
|
|
478
|
-
expect(live(
|
|
478
|
+
expect(live('echo "`rm -rf /`"', "files.destructive")).toBe(true);
|
|
479
479
|
expect(live(`echo '$(rm -rf /)'`, "files.destructive")).toBe(false);
|
|
480
480
|
});
|
|
481
481
|
|
|
@@ -485,3 +485,38 @@ describe("live", () => {
|
|
|
485
485
|
}
|
|
486
486
|
});
|
|
487
487
|
});
|
|
488
|
+
|
|
489
|
+
// The one class whose answer depends on WHOSE checkout would move. A conversation working in a copy of its own may
|
|
490
|
+
// stand wherever it likes — the drift that causes is reported at turn-settled, not refused here — but a turn sharing
|
|
491
|
+
// the owner's tree would move the owner's checkout under them, which is not its call to make.
|
|
492
|
+
describe("git.branch-switch", () => {
|
|
493
|
+
const shared = (command: string): string[] => classify(command);
|
|
494
|
+
const own = (command: string): string[] => classify(command, { ownCheckout: true });
|
|
495
|
+
|
|
496
|
+
test("catches both spellings of moving a shared checkout", () => {
|
|
497
|
+
for (const command of ["git checkout main", "git checkout -b ci/extension-admission", "git switch main", "git switch -c feature"]) {
|
|
498
|
+
expect(shared(command), command).toContain("git.branch-switch");
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("the conversation's own copy is its own to move", () => {
|
|
503
|
+
for (const command of ["git checkout main", "git switch -c feature"]) {
|
|
504
|
+
expect(own(command), command).not.toContain("git.branch-switch");
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Absent, not false: a caller that has not been taught the difference must get the guard rather than a hole.
|
|
509
|
+
test("a caller that cannot say whose checkout it is gets the guard", () => {
|
|
510
|
+
expect(classify("git checkout main", {})).toContain("git.branch-switch");
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("checkout restoring files moves nothing, and is not this", () => {
|
|
514
|
+
for (const command of ["git checkout -- src/app.ts", "git checkout .", "git checkout HEAD -- package.json"]) {
|
|
515
|
+
expect(shared(command), command).not.toContain("git.branch-switch");
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("the card points at the verb, not the whole line", () => {
|
|
520
|
+
expect(marked("cd repo && git switch main && pnpm build", "git.branch-switch")).toEqual(["git switch"]);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
@@ -28,6 +28,10 @@ export interface CommandContext {
|
|
|
28
28
|
readonly locus: CommandLocus;
|
|
29
29
|
// true/false/undefined for holding a credential; only false drops a class, undefined still counts as a hit.
|
|
30
30
|
readonly holdsSecret?: (path: string) => boolean | undefined;
|
|
31
|
+
// Whether this cwd is the conversation's OWN copy, which only an isolated turn has. Absent means the tree is shared
|
|
32
|
+
// with its owner, the fail-safe reading: a caller that has not been taught the difference gets the guard, not a
|
|
33
|
+
// hole. Read only by git.branch-switch, whose whole question is whose checkout would move.
|
|
34
|
+
readonly ownCheckout?: boolean;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
// The `g` twin of a table's patterns, built once: the tables stay flag-free since a surviving lastIndex is how a shared
|
|
@@ -63,6 +67,10 @@ const GIT_DESTRUCTIVE = [
|
|
|
63
67
|
/\bgit\s+filter-branch\b/,
|
|
64
68
|
];
|
|
65
69
|
|
|
70
|
+
// `switch` exists only to move HEAD, so every form of it counts. `checkout` is two commands wearing one name: with
|
|
71
|
+
// `-- <path>` or a bare `.` it restores files and moves nothing, and those are the common ones, so they are cut out.
|
|
72
|
+
const GIT_BRANCH_SWITCH = [/\bgit\s+switch\b/, /\bgit\s+checkout\b(?![^|;&]*\s--\s)(?![^|;&]*\s\.(?:\s|$))/];
|
|
73
|
+
|
|
66
74
|
// The credential is IN the command, not in a file; there is nothing for a filesystem to fact-check here.
|
|
67
75
|
const SECRET_REFERENCES = [/\{\{secret:[A-Za-z0-9_./-]+\}\}/];
|
|
68
76
|
|
|
@@ -251,6 +259,7 @@ const rootDeletes = (program: string, locus: CommandLocus): CommandSpan[] => [
|
|
|
251
259
|
|
|
252
260
|
// The g twins, built once at load: a card is minted per command held, classify runs per command typed.
|
|
253
261
|
const GIT_DESTRUCTIVE_G = globally(GIT_DESTRUCTIVE);
|
|
262
|
+
const GIT_BRANCH_SWITCH_G = globally(GIT_BRANCH_SWITCH);
|
|
254
263
|
const SECRET_REFERENCES_G = globally(SECRET_REFERENCES);
|
|
255
264
|
const CREDENTIAL_PATHS_G = globally(CREDENTIAL_PATHS);
|
|
256
265
|
const PACKAGE_PUBLISH_G = globally(PACKAGE_PUBLISH);
|
|
@@ -298,6 +307,8 @@ const credentialReads = (command: string, context: CommandContext): CommandSpan[
|
|
|
298
307
|
// One function per class; empty means the command is not in it, so membership and evidence are the same walk.
|
|
299
308
|
const MATCHES: Readonly<Record<CommandClass, (command: string, context: CommandContext) => CommandSpan[]>> = {
|
|
300
309
|
"git.destructive": (command) => spansOf(GIT_DESTRUCTIVE_G, command),
|
|
310
|
+
// The only class that can answer "not here": in its own copy a conversation's checkout is its own to move.
|
|
311
|
+
"git.branch-switch": (command, context) => (context.ownCheckout === true ? [] : spansOf(GIT_BRANCH_SWITCH_G, command)),
|
|
301
312
|
"files.destructive": (command) => [...recursiveForceRms(command), ...recursiveDeletes(command)],
|
|
302
313
|
"system.destructive": (command, context) => [...spansOf(BLOCK_DEVICE_G, command), ...rootDeletes(command, context.locus)],
|
|
303
314
|
"container.state": (command) => spansOf(CONTAINER_STATE_G, command),
|
|
@@ -323,6 +334,7 @@ export const classifyCommand = (command: string, context: CommandContext): Comma
|
|
|
323
334
|
// What the card says the command would do. The class name is a settings key, not a sentence to show a person.
|
|
324
335
|
export const COMMAND_CLASS_LABELS: Readonly<Record<CommandClass, string>> = {
|
|
325
336
|
"git.destructive": "rewrite or discard git history",
|
|
337
|
+
"git.branch-switch": "move a checkout you share with this conversation onto another branch",
|
|
326
338
|
"files.destructive": "delete files recursively",
|
|
327
339
|
"system.destructive": "wipe a disk, or delete a whole root directory",
|
|
328
340
|
"container.state": "delete a container volume or the data in it",
|
|
@@ -350,6 +362,10 @@ export const COMMAND_CLASS_PATTERNS: Readonly<Record<CommandClass, readonly Comm
|
|
|
350
362
|
{ code: "git branch -D" },
|
|
351
363
|
{ code: "git filter-branch" },
|
|
352
364
|
],
|
|
365
|
+
"git.branch-switch": [
|
|
366
|
+
{ code: "git switch <branch>" },
|
|
367
|
+
{ code: "git checkout <branch>", qualifier: "not `git checkout -- <path>` or `git checkout .`, which restore files and move nothing" },
|
|
368
|
+
],
|
|
353
369
|
"files.destructive": [
|
|
354
370
|
{ code: "rm -rf <path>" },
|
|
355
371
|
{ code: "fs.rm(<path>, { recursive: true })", qualifier: "also rmSync, rmdir, rmdirSync" },
|