@intentic/sandbox-contract 1.307.0 → 1.308.2
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 +12 -0
- package/dist/contracts/accounts.contract.d.ts +6 -6
- package/dist/contracts/accounts.contract.js +7 -7
- package/dist/contracts/accounts.contract.js.map +1 -1
- package/dist/contracts/activity.contract.d.ts +2 -2
- package/dist/contracts/activity.contract.js +3 -3
- package/dist/contracts/activity.contract.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +10 -10
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +21 -11
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +41 -27
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +44 -28
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +3 -3
- package/dist/contracts/approvals.contract.js +4 -4
- package/dist/contracts/approvals.contract.js.map +1 -1
- package/dist/contracts/areas.contract.d.ts +3 -3
- package/dist/contracts/areas.contract.d.ts.map +1 -1
- package/dist/contracts/areas.contract.js +5 -4
- package/dist/contracts/areas.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +11 -11
- package/dist/contracts/automations.contract.js +12 -12
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +13 -13
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +18 -14
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts +3 -3
- package/dist/contracts/chores.contract.js +4 -4
- package/dist/contracts/chores.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts +5 -5
- package/dist/contracts/ci.contract.js +6 -6
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/diff.contract.d.ts +1 -1
- package/dist/contracts/diff.contract.js +2 -2
- package/dist/contracts/diff.contract.js.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +4 -4
- package/dist/contracts/endpoints.contract.js +5 -5
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/exit.contract.d.ts +7 -7
- package/dist/contracts/exit.contract.d.ts.map +1 -1
- package/dist/contracts/exit.contract.js +9 -8
- package/dist/contracts/exit.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +17 -17
- package/dist/contracts/extensions.contract.js +18 -18
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts +47 -45
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +46 -46
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/history.contract.d.ts +4 -4
- package/dist/contracts/history.contract.js +5 -5
- package/dist/contracts/history.contract.js.map +1 -1
- package/dist/contracts/intentic.contract.d.ts +3 -3
- package/dist/contracts/intentic.contract.d.ts.map +1 -1
- package/dist/contracts/intentic.contract.js +5 -4
- package/dist/contracts/intentic.contract.js.map +1 -1
- package/dist/contracts/inventory.contract.d.ts +3 -3
- package/dist/contracts/inventory.contract.js +4 -4
- package/dist/contracts/inventory.contract.js.map +1 -1
- package/dist/contracts/issues.contract.d.ts +5 -5
- package/dist/contracts/issues.contract.js +6 -6
- package/dist/contracts/issues.contract.js.map +1 -1
- package/dist/contracts/logs.contract.d.ts +3 -3
- package/dist/contracts/logs.contract.d.ts.map +1 -1
- package/dist/contracts/logs.contract.js +6 -4
- package/dist/contracts/logs.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts +6 -6
- package/dist/contracts/loops.contract.js +7 -7
- package/dist/contracts/loops.contract.js.map +1 -1
- package/dist/contracts/netdisk.contract.d.ts +3 -3
- package/dist/contracts/netdisk.contract.d.ts.map +1 -1
- package/dist/contracts/netdisk.contract.js +5 -4
- package/dist/contracts/netdisk.contract.js.map +1 -1
- package/dist/contracts/panels.contract.d.ts +3 -3
- package/dist/contracts/panels.contract.js +4 -4
- package/dist/contracts/panels.contract.js.map +1 -1
- package/dist/contracts/personas.contract.d.ts +8 -8
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.js +10 -9
- package/dist/contracts/personas.contract.js.map +1 -1
- package/dist/contracts/ports.contract.d.ts +3 -3
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/ports.contract.js +5 -4
- package/dist/contracts/ports.contract.js.map +1 -1
- package/dist/contracts/prepush.contract.d.ts +3 -3
- package/dist/contracts/prepush.contract.js +4 -4
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/providers.contract.d.ts +2 -2
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/providers.contract.js +5 -3
- package/dist/contracts/providers.contract.js.map +1 -1
- package/dist/contracts/public.contract.d.ts +3 -3
- package/dist/contracts/public.contract.js +4 -4
- package/dist/contracts/public.contract.js.map +1 -1
- package/dist/contracts/push.contract.d.ts +4 -4
- package/dist/contracts/push.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.js +9 -5
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/safety.contract.d.ts +3 -3
- package/dist/contracts/safety.contract.js +4 -4
- package/dist/contracts/safety.contract.js.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +9 -9
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +13 -10
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +2 -2
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +5 -3
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +11 -9
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +11 -10
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts +4 -4
- package/dist/contracts/share.contract.js +5 -5
- package/dist/contracts/share.contract.js.map +1 -1
- package/dist/contracts/skills.contract.d.ts +5 -5
- package/dist/contracts/skills.contract.js +6 -6
- package/dist/contracts/skills.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +22 -19
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +27 -20
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +5 -5
- package/dist/contracts/translator.contract.js +6 -6
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/usage.contract.d.ts +4 -4
- package/dist/contracts/usage.contract.js +5 -5
- package/dist/contracts/usage.contract.js.map +1 -1
- package/dist/contracts/vpn.contract.d.ts +4 -4
- package/dist/contracts/vpn.contract.d.ts.map +1 -1
- package/dist/contracts/vpn.contract.js +6 -5
- package/dist/contracts/vpn.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts +9 -9
- package/dist/contracts/workflows.contract.js +10 -10
- package/dist/contracts/workflows.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +30 -30
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +46 -31
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/land-breakage.d.ts +4 -0
- package/dist/events/land-breakage.d.ts.map +1 -0
- package/dist/events/land-breakage.js +4 -0
- package/dist/events/land-breakage.js.map +1 -0
- package/dist/events/system-events.d.ts +2 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/index.d.ts +9379 -9350
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/protocol/procedure-types.d.ts +10 -0
- package/dist/protocol/procedure-types.d.ts.map +1 -0
- package/dist/protocol/procedure-types.js +2 -0
- package/dist/protocol/procedure-types.js.map +1 -0
- package/dist/protocol/raw-routes.d.ts +417 -0
- package/dist/protocol/raw-routes.d.ts.map +1 -0
- package/dist/protocol/raw-routes.js +140 -0
- package/dist/protocol/raw-routes.js.map +1 -0
- package/dist/protocol/route-meta.d.ts +18 -0
- package/dist/protocol/route-meta.d.ts.map +1 -0
- package/dist/protocol/route-meta.js +3 -0
- package/dist/protocol/route-meta.js.map +1 -0
- package/dist/protocol/routes.d.ts +3 -0
- package/dist/protocol/routes.d.ts.map +1 -1
- package/dist/protocol/routes.js +47 -14
- package/dist/protocol/routes.js.map +1 -1
- package/dist/schemas/agent.d.ts +33 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +22 -3
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +4 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +4 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +1 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +2 -2
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/git/git.d.ts +3 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/hosts.d.ts +1 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +1 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/settings.d.ts +1 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +4 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/state/definition.d.ts +4 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +2 -12
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/landed-commit.d.ts +4 -0
- package/dist/state/landed-commit.d.ts.map +1 -0
- package/dist/state/landed-commit.js +12 -0
- package/dist/state/landed-commit.js.map +1 -0
- package/package.json +5 -5
- package/src/contracts/accounts.contract.ts +7 -7
- package/src/contracts/activity.contract.ts +3 -3
- package/src/contracts/agent.contract.ts +25 -11
- package/src/contracts/agents.contract.ts +48 -30
- package/src/contracts/approvals.contract.ts +4 -4
- package/src/contracts/areas.contract.ts +5 -4
- package/src/contracts/automations.contract.ts +12 -12
- package/src/contracts/capabilities.contract.ts +23 -14
- package/src/contracts/chores.contract.ts +4 -4
- package/src/contracts/ci.contract.ts +6 -6
- package/src/contracts/diff.contract.ts +2 -2
- package/src/contracts/endpoints.contract.ts +5 -5
- package/src/contracts/exit.contract.ts +11 -8
- package/src/contracts/extensions.contract.ts +18 -18
- package/src/contracts/git.contract.ts +46 -46
- package/src/contracts/history.contract.ts +5 -5
- package/src/contracts/intentic.contract.ts +5 -4
- package/src/contracts/inventory.contract.ts +4 -4
- package/src/contracts/issues.contract.ts +6 -6
- package/src/contracts/logs.contract.ts +8 -4
- package/src/contracts/loops.contract.ts +7 -7
- package/src/contracts/netdisk.contract.ts +7 -4
- package/src/contracts/panels.contract.ts +4 -4
- package/src/contracts/personas.contract.ts +10 -9
- package/src/contracts/ports.contract.ts +6 -4
- package/src/contracts/prepush.contract.ts +4 -4
- package/src/contracts/providers.contract.ts +5 -3
- package/src/contracts/public.contract.ts +4 -4
- package/src/contracts/push.contract.ts +12 -5
- package/src/contracts/safety.contract.ts +4 -4
- package/src/contracts/secrets.contract.ts +16 -10
- package/src/contracts/sessions.contract.ts +6 -3
- package/src/contracts/settings.contract.ts +11 -10
- package/src/contracts/share.contract.ts +5 -5
- package/src/contracts/skills.contract.ts +6 -6
- package/src/contracts/system.contract.ts +32 -20
- package/src/contracts/translator.contract.ts +6 -6
- package/src/contracts/usage.contract.ts +5 -5
- package/src/contracts/vpn.contract.ts +8 -5
- package/src/contracts/workflows.contract.ts +10 -10
- package/src/contracts/workspace.contract.ts +49 -31
- package/src/events/land-breakage.ts +11 -0
- package/src/index.ts +23 -1
- package/src/models/capability-ledger.test.ts +1 -1
- package/src/protocol/procedure-types.ts +24 -0
- package/src/protocol/raw-routes.ts +191 -0
- package/src/protocol/route-meta.ts +39 -0
- package/src/protocol/routes.test.ts +65 -2
- package/src/protocol/routes.ts +88 -27
- package/src/schemas/agent.test.ts +66 -0
- package/src/schemas/agent.ts +184 -154
- package/src/schemas/agents.ts +6 -0
- package/src/schemas/devices.ts +2 -2
- package/src/schemas/hosts.ts +4 -0
- package/src/schemas/settings.ts +9 -0
- package/src/state/history-state.ts +14 -29
- package/src/state/landed-commit.test.ts +18 -0
- package/src/state/landed-commit.ts +17 -0
package/src/schemas/agent.ts
CHANGED
|
@@ -125,166 +125,167 @@ export const ForkedFromSchema = z.object({
|
|
|
125
125
|
files: z.enum(["then", "now"]),
|
|
126
126
|
});
|
|
127
127
|
export type ForkedFrom = z.infer<typeof ForkedFromSchema>;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
),
|
|
152
|
-
// Which provider serves the turn, absent = claude; a sessionId resumes only on the provider that minted it.
|
|
153
|
-
agent: AgentProviderSchema.optional().describe("Which model provider serves this turn. Leave it out for Claude."),
|
|
154
|
-
// Which agentic loop runs the turn, absent = provider's own; "claude-code" forces the SDK loop for any
|
|
155
|
-
// provider.
|
|
156
|
-
harness: AgentHarnessSchema.optional().describe("Which agentic loop runs the turn. Leave it out to use each provider's own."),
|
|
157
|
-
// Which connected account of that provider serves the turn; absent = the provider's first account.
|
|
158
|
-
account: z.string().optional().describe("Which of that provider's connected accounts pays for the turn. Leave it out for the first one."),
|
|
159
|
-
// Distinct from `account` (who pays): whose name the turn posts as; absent means something different for each
|
|
160
|
-
// mode.
|
|
161
|
-
actsAs: entryId.optional().describe("Which persona the turn speaks as out in the world. Not the same as which account pays for it."),
|
|
162
|
-
sessionId: z.string().optional().describe("Resume this provider session instead of starting a fresh one."),
|
|
163
|
-
// Stable id surviving provider/account/harness switches; keys the registry entry, the turn run, and the
|
|
164
|
-
// worktree when isolated.
|
|
165
|
-
conversationId: ConversationIdSchema.optional().describe(
|
|
166
|
-
"The conversation this turn belongs to. You choose it, it survives model switches, and it is how you address the conversation later. Naming one that does not exist opens it.",
|
|
128
|
+
// The turn's fields before the cross-field refinements below, so a subset (TurnProfileSchema) can be picked from them.
|
|
129
|
+
const AgentTurnFieldsSchema = z.object({
|
|
130
|
+
prompt: z.string().describe("What to say to the agent. May be empty if you are only attaching files."),
|
|
131
|
+
// Seeds a fresh registry entry's title; an existing entry's title always wins over this.
|
|
132
|
+
title: z
|
|
133
|
+
.string()
|
|
134
|
+
.max(80)
|
|
135
|
+
.optional()
|
|
136
|
+
.describe("A title for a conversation this turn is opening. Ignored for a conversation that already has one."),
|
|
137
|
+
// Already uploaded via /workspace/upload; Claude reads them via Read, Codex gets images as native input.
|
|
138
|
+
attachments: z
|
|
139
|
+
.array(z.string().min(1))
|
|
140
|
+
.max(20)
|
|
141
|
+
.optional()
|
|
142
|
+
.describe("Files to hand the agent along with the prompt, as workspace paths. Upload them first."),
|
|
143
|
+
// Read out of the prompt's own `@path` tokens rather than chosen: a tokenizer over pasted text guesses, so a
|
|
144
|
+
// miss here is dropped and only a chosen attachment can refuse the turn.
|
|
145
|
+
mentions: z
|
|
146
|
+
.array(z.string().min(1))
|
|
147
|
+
.max(MENTION_LIMIT)
|
|
148
|
+
.optional()
|
|
149
|
+
.describe(
|
|
150
|
+
"Workspace paths the prompt mentions with `@`. Unlike attachments, one that escapes the workspace or names no file is ignored rather than refused.",
|
|
167
151
|
),
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
152
|
+
// Which provider serves the turn, absent = claude; a sessionId resumes only on the provider that minted it.
|
|
153
|
+
agent: AgentProviderSchema.optional().describe("Which model provider serves this turn. Leave it out for Claude."),
|
|
154
|
+
// Which agentic loop runs the turn, absent = provider's own; "claude-code" forces the SDK loop for any
|
|
155
|
+
// provider.
|
|
156
|
+
harness: AgentHarnessSchema.optional().describe("Which agentic loop runs the turn. Leave it out to use each provider's own."),
|
|
157
|
+
// Which connected account of that provider serves the turn; absent = the provider's first account.
|
|
158
|
+
account: z.string().optional().describe("Which of that provider's connected accounts pays for the turn. Leave it out for the first one."),
|
|
159
|
+
// Distinct from `account` (who pays): whose name the turn posts as; absent means something different for each
|
|
160
|
+
// mode.
|
|
161
|
+
actsAs: entryId.optional().describe("Which persona the turn speaks as out in the world. Not the same as which account pays for it."),
|
|
162
|
+
sessionId: z.string().optional().describe("Resume this provider session instead of starting a fresh one."),
|
|
163
|
+
// Stable id surviving provider/account/harness switches; keys the registry entry, the turn run, and the
|
|
164
|
+
// worktree when isolated.
|
|
165
|
+
conversationId: ConversationIdSchema.optional().describe(
|
|
166
|
+
"The conversation this turn belongs to. You choose it, it survives model switches, and it is how you address the conversation later. Naming one that does not exist opens it.",
|
|
167
|
+
),
|
|
168
|
+
// Runs in the conversation's own git worktree (created lazily) instead of shared /work; requires
|
|
169
|
+
// conversationId.
|
|
170
|
+
isolated: z
|
|
171
|
+
.boolean()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe(
|
|
174
|
+
"Work in this conversation's own private copy of the repos rather than the shared tree, so several agents can work at once. Needs a conversation id.",
|
|
188
175
|
),
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
.
|
|
196
|
-
.describe(
|
|
197
|
-
"Pin a new private copy to these exact commits instead of today's workspace. Used when several agents must start from identical files.",
|
|
198
|
-
),
|
|
199
|
-
// Overrides landing for this turn only; a workflow step sets false so candidates can't leak in before synthesis
|
|
200
|
-
// compares them.
|
|
201
|
-
autoLand: z
|
|
202
|
-
.boolean()
|
|
203
|
-
.optional()
|
|
204
|
-
.describe(
|
|
205
|
-
"Whether this turn's work merges into the workspace when it finishes. Overrides the conversation's own setting for this turn only.",
|
|
206
|
-
),
|
|
207
|
-
// Which per-job model list answers when nobody named a model or provider (turn-resume.ts); names the job, not a
|
|
208
|
-
// tier, and never overrides an explicit pick.
|
|
209
|
-
runRole: ModelRoleSchema.optional().describe(
|
|
210
|
-
"What started this turn, when it was not a person typing: which of the sandbox's per-job model lists answers for it. Only used when the turn names no model of its own.",
|
|
176
|
+
// Decided like `isolated`: the first turn's choice, latched. A persona's own start folder wins over it.
|
|
177
|
+
startIn: z
|
|
178
|
+
.string()
|
|
179
|
+
.max(200)
|
|
180
|
+
.optional()
|
|
181
|
+
.describe(
|
|
182
|
+
"Which folder the conversation opens in, relative to the workspace root; the project it belongs to. Decided on the first turn. A persona that names its own start folder wins.",
|
|
211
183
|
),
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
184
|
+
// Decided like `isolated`: the request's choice on the first turn, the registry's after. `runner` implies
|
|
185
|
+
// isolation; absent means local.
|
|
186
|
+
placement: AgentPlacementSchema.optional().describe(
|
|
187
|
+
"Where this conversation runs: this sandbox (leave it out), or a paired runner by id. Decided on the first turn; later turns follow the conversation.",
|
|
188
|
+
),
|
|
189
|
+
// Pins a new isolated conversation's worktree to these commits; a workflow step's candidates share one snapshot
|
|
190
|
+
// and skip the ordinary rebase.
|
|
191
|
+
worktreeBase: z
|
|
192
|
+
.array(RepoBaseSchema)
|
|
193
|
+
.min(1)
|
|
194
|
+
.max(50)
|
|
195
|
+
.optional()
|
|
196
|
+
.describe(
|
|
197
|
+
"Pin a new private copy to these exact commits instead of today's workspace. Used when several agents must start from identical files.",
|
|
215
198
|
),
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
keep: z.number().int().nonnegative().describe("How many of that conversation's messages to copy in before this turn runs."),
|
|
224
|
-
files: z
|
|
225
|
-
.enum(["then", "now"])
|
|
226
|
-
.describe(
|
|
227
|
-
'Which files the fork opens on: "now" is the workspace as it stands, "then" is the files as they were at the cut, which needs a private copy.',
|
|
228
|
-
),
|
|
229
|
-
})
|
|
230
|
-
.optional()
|
|
231
|
-
.describe("Where this conversation was cut from, on its first turn only. Only the client knows this, so only the client can say it."),
|
|
232
|
-
// The browser sends the chosen model per turn; the provider token is the sandbox's own stored credential.
|
|
233
|
-
model: z.string().optional().describe("Which model to use. Leave it out for the provider's default."),
|
|
234
|
-
// Audience only, never model routing: `runRole` is what fills a model in. A run somebody pressed and is
|
|
235
|
-
// watching leaves this off, however little of it a person typed.
|
|
236
|
-
unattended: z
|
|
237
|
-
.boolean()
|
|
238
|
-
.optional()
|
|
239
|
-
.describe(
|
|
240
|
-
"Nobody is watching this turn: a schedule, a queue or another agent started it and no chat is open on it. A card that needs a person is refused rather than raised, plan mode and the terminal hand-off are withheld, and the sandbox's signed-in accounts stay out of it unless a persona carries them.",
|
|
241
|
-
),
|
|
242
|
-
// Outside content caused this turn, naming the source; distinct from `unattended` (whether anyone is watching).
|
|
243
|
-
outsideWake: z
|
|
244
|
-
.string()
|
|
245
|
-
.min(1)
|
|
246
|
-
.optional()
|
|
247
|
-
.describe(
|
|
248
|
-
"Content from outside caused this turn, and what to call the source. It is what makes the sandbox treat the turn as carrying somebody else's words.",
|
|
249
|
-
),
|
|
250
|
-
// How tool calls are gated for this turn (the SDK's permissionMode, verbatim):
|
|
251
|
-
// plan: propose → approve → execute; the proposing half asks nothing, it only withholds writes
|
|
252
|
-
// default: prompts per tool on the permission side channel, the one mode that interrupts anybody
|
|
253
|
-
// bypassPermissions: runs everything
|
|
254
|
-
// The agent can move itself between modes mid-turn, riding back as a `mode` frame.
|
|
255
|
-
permissionMode: PermissionModeSchema.optional().describe(
|
|
256
|
-
"How tool calls are gated: ask before each tool, propose a plan first, or run everything. The agent can move itself between these mid-turn.",
|
|
199
|
+
// Overrides landing for this turn only; a workflow step sets false so candidates can't leak in before synthesis
|
|
200
|
+
// compares them.
|
|
201
|
+
autoLand: z
|
|
202
|
+
.boolean()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe(
|
|
205
|
+
"Whether this turn's work merges into the workspace when it finishes. Overrides the conversation's own setting for this turn only.",
|
|
257
206
|
),
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
.describe(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
207
|
+
// Which per-job model list answers when nobody named a model or provider (turn-resume.ts); names the job, not a
|
|
208
|
+
// tier, and never overrides an explicit pick.
|
|
209
|
+
runRole: ModelRoleSchema.optional().describe(
|
|
210
|
+
"What started this turn, when it was not a person typing: which of the sandbox's per-job model lists answers for it. Only used when the turn names no model of its own.",
|
|
211
|
+
),
|
|
212
|
+
// Set only by the daemon's own dispatchers, never a client; requires conversationId to record it on.
|
|
213
|
+
origin: AgentOriginSchema.optional().describe(
|
|
214
|
+
"Set by the sandbox alone: this turn opened a conversation on behalf of a message from outside rather than a person.",
|
|
215
|
+
),
|
|
216
|
+
// No `history` field: a provider/account/harness switch carries no transcript over the wire; the daemon seeds
|
|
217
|
+
// the new session from its own conversationId-keyed record.
|
|
218
|
+
// Where a fork was cut from, on its first turn only; requires conversationId, and files:"then" implies
|
|
219
|
+
// isolated.
|
|
220
|
+
forkOf: z
|
|
221
|
+
.object({
|
|
222
|
+
conversationId: ConversationIdSchema.describe("The conversation this one was cut from."),
|
|
223
|
+
keep: z.number().int().nonnegative().describe("How many of that conversation's messages to copy in before this turn runs."),
|
|
224
|
+
files: z
|
|
225
|
+
.enum(["then", "now"])
|
|
226
|
+
.describe(
|
|
227
|
+
'Which files the fork opens on: "now" is the workspace as it stands, "then" is the files as they were at the cut, which needs a private copy.',
|
|
228
|
+
),
|
|
229
|
+
})
|
|
230
|
+
.optional()
|
|
231
|
+
.describe("Where this conversation was cut from, on its first turn only. Only the client knows this, so only the client can say it."),
|
|
232
|
+
// The browser sends the chosen model per turn; the provider token is the sandbox's own stored credential.
|
|
233
|
+
model: z.string().optional().describe("Which model to use. Leave it out for the provider's default."),
|
|
234
|
+
// Audience only, never model routing: `runRole` is what fills a model in. A run somebody pressed and is
|
|
235
|
+
// watching leaves this off, however little of it a person typed.
|
|
236
|
+
unattended: z
|
|
237
|
+
.boolean()
|
|
238
|
+
.optional()
|
|
239
|
+
.describe(
|
|
240
|
+
"Nobody is watching this turn: a schedule, a queue or another agent started it and no chat is open on it. A card that needs a person is refused rather than raised, plan mode and the terminal hand-off are withheld, and the sandbox's signed-in accounts stay out of it unless a persona carries them.",
|
|
286
241
|
),
|
|
287
|
-
|
|
242
|
+
// Outside content caused this turn, naming the source; distinct from `unattended` (whether anyone is watching).
|
|
243
|
+
outsideWake: z
|
|
244
|
+
.string()
|
|
245
|
+
.min(1)
|
|
246
|
+
.optional()
|
|
247
|
+
.describe(
|
|
248
|
+
"Content from outside caused this turn, and what to call the source. It is what makes the sandbox treat the turn as carrying somebody else's words.",
|
|
249
|
+
),
|
|
250
|
+
// How tool calls are gated for this turn (the SDK's permissionMode, verbatim):
|
|
251
|
+
// plan: propose → approve → execute; the proposing half asks nothing, it only withholds writes
|
|
252
|
+
// default: prompts per tool on the permission side channel, the one mode that interrupts anybody
|
|
253
|
+
// bypassPermissions: runs everything
|
|
254
|
+
// The agent can move itself between modes mid-turn, riding back as a `mode` frame.
|
|
255
|
+
permissionMode: PermissionModeSchema.optional().describe(
|
|
256
|
+
"How tool calls are gated: ask before each tool, propose a plan first, or run everything. The agent can move itself between these mid-turn.",
|
|
257
|
+
),
|
|
258
|
+
// Narrows the turn to these tool names (the SDK option, not the daemon's MCP `tools`/servers); absent means
|
|
259
|
+
// everything the runtime has.
|
|
260
|
+
allowedTools: z
|
|
261
|
+
.array(z.string().min(1))
|
|
262
|
+
.optional()
|
|
263
|
+
.describe(
|
|
264
|
+
"Narrow the turn to these tools. Leave it out for everything the runtime has. For a turn driven by an outside message this list is the real boundary, because prompt wording is only advice.",
|
|
265
|
+
),
|
|
266
|
+
effort: z.string().optional().describe("How hard the model should think, where the provider offers a choice."),
|
|
267
|
+
thinking: z.boolean().optional().describe("Whether to show the model's reasoning as it works."),
|
|
268
|
+
// Requests the harness serve this turn faster at a higher price; a request, not a promise, the `fast_mode`
|
|
269
|
+
// frame reports what happened.
|
|
270
|
+
fast: z
|
|
271
|
+
.boolean()
|
|
272
|
+
.optional()
|
|
273
|
+
.describe(
|
|
274
|
+
"Ask for the same work at a higher rate for a higher price. A request rather than a promise: the answer says what actually happened.",
|
|
275
|
+
),
|
|
276
|
+
// Set by the composer on the one turn whose model the Auto judge chose; the daemon only records it.
|
|
277
|
+
autoPicked: z
|
|
278
|
+
.boolean()
|
|
279
|
+
.optional()
|
|
280
|
+
.describe(
|
|
281
|
+
"Whether this turn's model was chosen for you by reading the conversation's opening message, rather than picked by hand. Recorded so the choice can be judged later against what you did next.",
|
|
282
|
+
),
|
|
283
|
+
// The opt-in editor context chip: what the user is looking at, folded into the prompt daemon-side.
|
|
284
|
+
editorContext: EditorContextSchema.optional().describe(
|
|
285
|
+
'What the user has open in their editor, folded into the prompt so that pointing words like "this" resolve.',
|
|
286
|
+
),
|
|
287
|
+
});
|
|
288
|
+
export const AgentTurnSchema = AgentTurnFieldsSchema
|
|
288
289
|
// An attachment-only send (no text) is legal; an entirely empty turn is not.
|
|
289
290
|
.refine((turn) => turn.prompt.trim().length > 0 || (turn.attachments?.length ?? 0) > 0, {
|
|
290
291
|
message: "prompt or attachments required",
|
|
@@ -306,6 +307,35 @@ export const AgentTurnSchema = z
|
|
|
306
307
|
message: 'forkOf.files "then" requires isolated',
|
|
307
308
|
});
|
|
308
309
|
export type AgentTurn = z.infer<typeof AgentTurnSchema>;
|
|
310
|
+
// Which turn this is, as opposed to what it says: who serves it and how (agent, harness, account, model and its knobs),
|
|
311
|
+
// as whom (actsAs), where (isolated), whether anyone watches (unattended) and for which job (runRole). A turn that
|
|
312
|
+
// continues another (a resume, a wake, a nudge, a child's follow-up) carries this whole, never a hand-picked part.
|
|
313
|
+
export const TurnProfileSchema = AgentTurnFieldsSchema.pick({
|
|
314
|
+
agent: true,
|
|
315
|
+
harness: true,
|
|
316
|
+
account: true,
|
|
317
|
+
model: true,
|
|
318
|
+
effort: true,
|
|
319
|
+
thinking: true,
|
|
320
|
+
fast: true,
|
|
321
|
+
actsAs: true,
|
|
322
|
+
isolated: true,
|
|
323
|
+
unattended: true,
|
|
324
|
+
runRole: true,
|
|
325
|
+
});
|
|
326
|
+
export type TurnProfile = z.infer<typeof TurnProfileSchema>;
|
|
327
|
+
const PROFILE_KEYS = Object.keys(TurnProfileSchema.shape) as readonly (keyof TurnProfile)[];
|
|
328
|
+
// Read as `=== true` everywhere, so false and absent are one value; carrying a false would state a placement or an
|
|
329
|
+
// audience the turn never named.
|
|
330
|
+
const TRUE_ONLY: ReadonlySet<keyof TurnProfile> = new Set(["isolated", "unattended"]);
|
|
331
|
+
/* THE PROFILE A TURN STATES: absent stays absent (no `undefined` keys), and a `=== true` flag travels only when set. */
|
|
332
|
+
export const profileOf = (turn: TurnProfile): TurnProfile =>
|
|
333
|
+
Object.fromEntries(
|
|
334
|
+
PROFILE_KEYS.flatMap((key) => {
|
|
335
|
+
const value = turn[key];
|
|
336
|
+
return value === undefined || (TRUE_ONLY.has(key) && value !== true) ? [] : [[key, value]];
|
|
337
|
+
}),
|
|
338
|
+
) as TurnProfile;
|
|
309
339
|
/* A MODEL CHOSEN FOR ONE SURFACE-STARTED RUN, what the caret on the shared run button (<AgentRunButton>) sends along with the click that starts it. */
|
|
310
340
|
export const AgentRunPickSchema = z
|
|
311
341
|
.object({
|
package/src/schemas/agents.ts
CHANGED
|
@@ -109,6 +109,12 @@ export const LandedMessageSchema = z.object({
|
|
|
109
109
|
.string()
|
|
110
110
|
.optional()
|
|
111
111
|
.describe("What this change takes away, for anything already relying on it. Nearly always absent: it is for removals, not for additions."),
|
|
112
|
+
// Present only when the conversation said why a test it weakened should stay weaker; the push gate reads it as the
|
|
113
|
+
// weakening's declaration.
|
|
114
|
+
testNote: z
|
|
115
|
+
.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe("Why a test this change weakened is meant to be weaker, in the conversation's own words. Nearly always absent."),
|
|
112
118
|
});
|
|
113
119
|
export type LandedMessage = z.infer<typeof LandedMessageSchema>;
|
|
114
120
|
// One model's turn in the drafting walk (agent/role-model.ts tries connected models in order), in spend order:
|
package/src/schemas/devices.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// What one of the user's own machines is running.
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { hostEntryOf, hostEnvironmentOf, type DeviceFacts, DeviceFactsSchema,
|
|
3
|
+
import { hostEntryOf, hostEnvironmentOf, type DeviceFacts, DeviceFactsSchema, userDistrosOf, WslEnvironmentSchema } from "./hosts.js";
|
|
4
4
|
import { DEV_VERSION } from "../state/versions.js";
|
|
5
5
|
// Desktop-sync report shape shared by the agent, daemon and browser, produced only by the agent's own `deviceReport`.
|
|
6
6
|
// The agent never reports `sandboxes`; the docker half is filled in by whoever reads the report, scoped to the reader's
|
|
@@ -596,7 +596,7 @@ export const pathReach = (platform: string | undefined, facts: DeviceFacts | und
|
|
|
596
596
|
if (platform === undefined || (platform === "windows") === windowsPath(path)) {
|
|
597
597
|
return { kind: "direct" };
|
|
598
598
|
}
|
|
599
|
-
const distros = platform === "windows" ? (facts
|
|
599
|
+
const distros = platform === "windows" ? userDistrosOf(facts) : [];
|
|
600
600
|
const only = distros.length === 1 ? distros[0] : undefined;
|
|
601
601
|
return only === undefined ? { kind: "none", distros } : { kind: "wsl", distro: only };
|
|
602
602
|
};
|
package/src/schemas/hosts.ts
CHANGED
|
@@ -41,6 +41,10 @@ export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
|
|
|
41
41
|
// Docker Desktop's own distros: `wsl -l -q` lists them like any other, and none ever runs an agent or holds a checkout.
|
|
42
42
|
export const WSL_SYSTEM_DISTROS: ReadonlySet<string> = new Set(["docker-desktop", "docker-desktop-data"]);
|
|
43
43
|
|
|
44
|
+
// The distros a Windows side lists that are the user's own: the only ones any screen offers, crosses into or boots.
|
|
45
|
+
export const userDistrosOf = (facts: Pick<DeviceFacts, "wslDistros"> | undefined): string[] =>
|
|
46
|
+
(facts?.wslDistros ?? []).filter((distro) => !WSL_SYSTEM_DISTROS.has(distro));
|
|
47
|
+
|
|
44
48
|
// One folder, two names. Windows sees a distro's files under a UNC share and a distro sees the Windows drives under
|
|
45
49
|
// /mnt, so a path handed across the boundary is translated here rather than by hand at every call site.
|
|
46
50
|
export const wslPathOf = (windowsPath: string): string | undefined => {
|
package/src/schemas/settings.ts
CHANGED
|
@@ -389,6 +389,15 @@ export const SandboxSettingsSchema = z.object({
|
|
|
389
389
|
),
|
|
390
390
|
// Worth it since the container is recreated on every update or environment approval — otherwise approving a
|
|
391
391
|
// Dockerfile change costs the run that asked for it.
|
|
392
|
+
// What happens to breakage found after the work that caused it has left the turn: a land that turns the main tree's own
|
|
393
|
+
// check red is sent back to the conversation that landed it, and main's CI staying red on one failure gets a fix agent
|
|
394
|
+
// once pushes go quiet, with a fleet failure re-run instead. Off, both are only reported.
|
|
395
|
+
autoRepair: z
|
|
396
|
+
.boolean()
|
|
397
|
+
.default(true)
|
|
398
|
+
.describe(
|
|
399
|
+
"Whether breakage found after the work left its turn is repaired without asking: a land that turns the main tree's check red goes back to the conversation that landed it, and main's CI staying red on the same failure gets a fix agent once pushes go quiet. A failure on the CI fleet itself is re-run once instead. Off, both are only reported.",
|
|
400
|
+
),
|
|
392
401
|
autoResumeOnRestart: z
|
|
393
402
|
.boolean()
|
|
394
403
|
.default(false)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { StateFile } from "./state-portability.js";
|
|
2
2
|
|
|
3
|
-
// /history holds daemon state outside /work's `.intentic/`: real git dirs (in-tree .git is a pointer file), the fleet
|
|
4
|
-
//
|
|
5
|
-
// history-state-coverage.test.ts enforces this list stays complete.
|
|
3
|
+
// /history holds daemon state outside /work's `.intentic/`: real git dirs (in-tree .git is a pointer file), the fleet's
|
|
4
|
+
// database and each conversation's directory, ledgers, checkpoint scopes, isolated checkouts. Nothing here is watched,
|
|
5
|
+
// unlike `.intentic`. history-state-coverage.test.ts enforces this list stays complete.
|
|
6
6
|
|
|
7
7
|
// historyRoot-relative paths, forward-slash, matched by prefix; a directory keeps its trailing slash so it can't
|
|
8
8
|
// prefix-match a sibling file.
|
|
@@ -13,22 +13,15 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
13
13
|
{ path: "gits/", portability: "carry" },
|
|
14
14
|
// Checkpoint timeline (one bare repo per scope); lets "restore to before that turn" still reach back.
|
|
15
15
|
{ path: "scopes/", portability: "carry" },
|
|
16
|
-
// The fleet
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
{ path: "
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
// Carried
|
|
24
|
-
|
|
25
|
-
{ path: "system-prompts/", portability: "carry" },
|
|
26
|
-
// One runtime session store per fenced conversation, holding for it what `.intentic/records/sessions/claude/` holds
|
|
27
|
-
// for every other: its own transcripts, plans, backups and checklists, kept off /work so no other conversation's
|
|
28
|
-
// namespace can reach them. Carried for the same reason that one is.
|
|
29
|
-
{ path: "sessions/", portability: "carry" },
|
|
30
|
-
// What each message can restore to (a checkpoint or isolated commit); the join between transcripts and scopes.
|
|
31
|
-
{ path: "turn-checkpoints.json", portability: "carry" },
|
|
16
|
+
// The fleet and everything keyed by a conversation that must agree with it: each card's record, its checkout's
|
|
17
|
+
// per-repo provenance, what each message restores to, its turn in flight and its armed watches, plus automation
|
|
18
|
+
// fires in flight. Packed as a snapshot taken for the bundle, never as the live file and its WAL, and landed row
|
|
19
|
+
// by row into the target's own database. Watches carry safely: they hold no credential, only names.
|
|
20
|
+
{ path: "conversations.db", portability: "carry" },
|
|
21
|
+
// One directory per conversation: its transcript, what its last turn was told before its own words, and a fenced
|
|
22
|
+
// conversation's own runtime session store (what `.intentic/records/sessions/claude/` holds for every other).
|
|
23
|
+
// Carried whole, like the conversation it is.
|
|
24
|
+
{ path: "conversations/", portability: "carry" },
|
|
32
25
|
// Index of published-page conversations; the pages travel with /work anyway, this makes one withdrawable.
|
|
33
26
|
{ path: "shares.json", portability: "carry" },
|
|
34
27
|
{ path: "activity.jsonl", portability: "carry" },
|
|
@@ -56,22 +49,14 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
56
49
|
|
|
57
50
|
/* ---- regenerated by the target ---- */
|
|
58
51
|
|
|
59
|
-
// Deliberately omitted: the branch lives in gits/, the registry in
|
|
52
|
+
// Deliberately omitted: the branch lives in gits/, the registry in conversations.db; regenerated on the next turn.
|
|
60
53
|
{
|
|
61
54
|
path: "worktrees/",
|
|
62
55
|
portability: "derived",
|
|
63
56
|
note: "Each conversation re-attaches its checkout from its branch on its next turn.",
|
|
64
57
|
},
|
|
65
|
-
// Search phrase index, derived from transcripts
|
|
58
|
+
// Search phrase index, derived from the conversations' transcripts; rebuilt by the first boot's backfill instead.
|
|
66
59
|
{ path: "said-index/", portability: "derived", note: "Rebuilt from the carried transcripts by the first boot's backfill." },
|
|
67
|
-
// What each conversation was last told (prompt-record.ts). Tens of kilobytes per conversation, rewritten by every
|
|
68
|
-
// turn — and a prompt composed on THIS sandbox names its paths, devices and accounts, so carrying it would describe
|
|
69
|
-
// the machine the bundle left rather than the one it landed on.
|
|
70
|
-
{
|
|
71
|
-
path: "system-prompts/",
|
|
72
|
-
portability: "derived",
|
|
73
|
-
note: "Each conversation records what it was told again on its next turn.",
|
|
74
|
-
},
|
|
75
60
|
// What each connected machine and browser publishes as its tools, so one that is asleep still appears in a turn
|
|
76
61
|
// (peers/peer-tool-memory.ts). Re-asked of every peer the moment it reconnects, and about software that stayed
|
|
77
62
|
// behind on somebody else's computer, so it is not worth carrying.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { declaredTestNote, landedCommitMessage } from "./landed-commit.js";
|
|
3
|
+
|
|
4
|
+
// The push gate reads a weakening's declaration off the commit (assertion-ratchet.mjs), so the trailer must survive
|
|
5
|
+
// the trip from the conversation's last word into the message a land commits under.
|
|
6
|
+
|
|
7
|
+
test("a landed message commits as its subject and one trailer paragraph, the test note included", () => {
|
|
8
|
+
expect(landedCommitMessage({ subject: "fix: tabs" })).toBe("fix: tabs");
|
|
9
|
+
expect(landedCommitMessage({ subject: "feat: tabs", note: "Tabs remember.", breaking: "Old API gone.", testNote: "Rows became a table." })).toBe(
|
|
10
|
+
"feat: tabs\n\nRelease-Note: Tabs remember.\nBreaking-Note: Old API gone.\nTest-Note: Rows became a table.",
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("a Test-Note is read only from a line that starts with one and says something", () => {
|
|
15
|
+
expect(declaredTestNote("Done.\n\nTest-Note: the prose assertion became a structural one\n")).toBe("the prose assertion became a structural one");
|
|
16
|
+
expect(declaredTestNote("I kept a Test-Note: nowhere near the start")).toBeUndefined();
|
|
17
|
+
expect(declaredTestNote("Test-Note: \n")).toBeUndefined();
|
|
18
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LandedMessage } from "../schemas/agents.js";
|
|
2
|
+
|
|
3
|
+
// The commit a landed message makes: its subject, then every trailer in one paragraph, since git reads only the last
|
|
4
|
+
// block of a message as trailers.
|
|
5
|
+
export const landedCommitMessage = (landed: LandedMessage): string => {
|
|
6
|
+
const trailers = [
|
|
7
|
+
landed.note === undefined ? "" : `Release-Note: ${landed.note}`,
|
|
8
|
+
landed.breaking === undefined ? "" : `Breaking-Note: ${landed.breaking}`,
|
|
9
|
+
landed.testNote === undefined ? "" : `Test-Note: ${landed.testNote}`,
|
|
10
|
+
]
|
|
11
|
+
.filter((line) => line !== "")
|
|
12
|
+
.join("\n");
|
|
13
|
+
return [landed.subject, trailers].filter((part) => part !== "").join("\n\n");
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// The `Test-Note:` line a conversation ended its last message with, when it declared a weakened test on purpose.
|
|
17
|
+
export const declaredTestNote = (said: string): string | undefined => /^Test-Note:[ \t]*(\S.*)$/m.exec(said)?.[1]?.trim();
|