@stigmer/runner 3.20.0 → 3.21.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/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +7 -0
- package/dist/__test-utils__/execution-record-fixture.js +10 -1
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
- package/dist/__test-utils__/hermetic-activity.d.ts +7 -4
- package/dist/__test-utils__/hermetic-activity.js +12 -2
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- package/dist/__test-utils__/turn-input-fixture.js +0 -1
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.js +8 -3
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +1 -3
- package/dist/activities/execute-deep-agent/prompt-builder.js +2 -3
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/generate-session-subject.d.ts +2 -1
- package/dist/activities/generate-session-subject.js +18 -10
- package/dist/activities/generate-session-subject.js.map +1 -1
- package/dist/harness/turn-context.js +3 -1
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/shared/blueprint-resolver.d.ts +14 -4
- package/dist/shared/blueprint-resolver.js +23 -10
- package/dist/shared/blueprint-resolver.js.map +1 -1
- package/dist/shared/builtin-assistant-prompt.d.ts +30 -0
- package/dist/shared/builtin-assistant-prompt.js +36 -0
- package/dist/shared/builtin-assistant-prompt.js.map +1 -0
- package/package.json +5 -5
- package/src/__test-utils__/execution-record-fixture.ts +17 -1
- package/src/__test-utils__/hermetic-activity.ts +19 -6
- package/src/__test-utils__/turn-input-fixture.ts +0 -1
- package/src/activities/__tests__/generate-session-subject.test.ts +14 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.no-instructions.prompt.md +142 -0
- package/src/activities/execute-cursor/__tests__/hermetic/built-in-assistant.test.ts +127 -0
- package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +14 -1
- package/src/activities/execute-cursor/prompt-builder.ts +8 -3
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +2 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/built-in-assistant.test.ts +101 -0
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +1 -1
- package/src/activities/execute-deep-agent/prompt-builder.ts +3 -5
- package/src/activities/generate-session-subject.ts +20 -13
- package/src/harness/turn-context.ts +3 -1
- package/src/shared/__tests__/blueprint-resolver.test.ts +78 -8
- package/src/shared/blueprint-resolver.ts +25 -12
- package/src/shared/builtin-assistant-prompt.ts +38 -0
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type StandingSectionKind,
|
|
49
49
|
type VisionPromptInfo,
|
|
50
50
|
} from "../../shared/prompt-sections.js";
|
|
51
|
+
import { effectiveInstructions } from "../../shared/builtin-assistant-prompt.js";
|
|
51
52
|
import { formatTurnRecoveryText } from "./turn-recovery.js";
|
|
52
53
|
import type { SkillMetadata } from "../../shared/skill-resolver.js";
|
|
53
54
|
import type { AgentResolution, AgentResolutionReason } from "./session-lifecycle.js";
|
|
@@ -201,9 +202,10 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
201
202
|
sections.push(implementPlan);
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
// The blueprint's instructions, or the built-in assistant's when it
|
|
206
|
+
// carries none (a session with no agent, or an agent that left them
|
|
207
|
+
// empty) — the one text both harnesses read on this arm.
|
|
208
|
+
sections.push(formatInstructions(effectiveInstructions(options.instructions)));
|
|
207
209
|
|
|
208
210
|
if (options.skills.length > 0) {
|
|
209
211
|
sections.push(formatSkillsSection(options.skills));
|
|
@@ -241,6 +243,9 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
241
243
|
sections.push(formatReferencedFiles(options.workspaceFileRefs));
|
|
242
244
|
}
|
|
243
245
|
|
|
246
|
+
// The engine-facing house rules ride along when the blueprint itself
|
|
247
|
+
// said nothing about how to respond; an author's instructions are taken
|
|
248
|
+
// to have covered them.
|
|
244
249
|
if (!options.instructions) {
|
|
245
250
|
sections.push(formatResponseRules());
|
|
246
251
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
You are
|
|
1
|
+
You are Stigmer's assistant. Help with whatever the person brings: answer, reason, write and edit, and use the tools and skills attached to this conversation when they help.
|
|
2
|
+
Be direct and concrete. Say what you do not know. Ask when a request is ambiguous. Prefer changing what exists over creating anew.
|
|
2
3
|
|
|
3
4
|
## Response rules
|
|
4
5
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic pin: the BUILT-IN ASSISTANT through the whole `ExecuteDeepAgent`
|
|
3
|
+
* activity — a session that names no agent instance, so the blueprint chain
|
|
4
|
+
* stops at the session (`shared/blueprint-resolver.ts`), the control plane
|
|
5
|
+
* is never asked for an instance or an agent, and the system prompt opens
|
|
6
|
+
* with the one built-in text (`shared/builtin-assistant-prompt.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Invariant pinned: the agent-less record is a first-class turn, not an
|
|
9
|
+
* error arm — IN_PROGRESS then COMPLETED with the scripted answer, the same
|
|
10
|
+
* shape as `plain-turn.test.ts`; and the words the model saw are the shared
|
|
11
|
+
* ones, read off the scripted model's `systemPrompt` context. The status
|
|
12
|
+
* itself is not goldened here: nothing agent-shaped reaches the status, so
|
|
13
|
+
* the plain-turn golden already photographs it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
17
|
+
import { ExecutionPhase, MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
|
+
|
|
19
|
+
vi.mock("../../../../shared/model-client.js", async () =>
|
|
20
|
+
(await import("../../__test-utils__/scripted-model-module.js")).scriptedModelClientModule(),
|
|
21
|
+
);
|
|
22
|
+
vi.mock("../../../../client/stigmer-client.js", async () =>
|
|
23
|
+
(await import("../../../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
ScriptedClock,
|
|
28
|
+
createHermeticEnvironment,
|
|
29
|
+
type HermeticEnvironment,
|
|
30
|
+
} from "../../../../__test-utils__/hermetic-activity.js";
|
|
31
|
+
import { stubRegistryFetch } from "../../../../__test-utils__/model-registry-fixture.js";
|
|
32
|
+
import { BUILT_IN_ASSISTANT_INSTRUCTIONS } from "../../../../shared/builtin-assistant-prompt.js";
|
|
33
|
+
import {
|
|
34
|
+
beginDeepAgentScenario,
|
|
35
|
+
deepAgentExecutionRecord,
|
|
36
|
+
runDeepAgentTurn,
|
|
37
|
+
} from "../../__test-utils__/hermetic-deep-agent.js";
|
|
38
|
+
|
|
39
|
+
const USER_MESSAGE = "Say hello.";
|
|
40
|
+
const ASSISTANT_TEXT = "Hello from the built-in assistant.";
|
|
41
|
+
|
|
42
|
+
describe("ExecuteDeepAgent hermetic — the built-in assistant", () => {
|
|
43
|
+
let env: HermeticEnvironment;
|
|
44
|
+
let registry: ReturnType<typeof stubRegistryFetch>;
|
|
45
|
+
const clock = new ScriptedClock();
|
|
46
|
+
|
|
47
|
+
beforeAll(() => {
|
|
48
|
+
env = createHermeticEnvironment();
|
|
49
|
+
registry = stubRegistryFetch();
|
|
50
|
+
clock.install();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterAll(() => {
|
|
54
|
+
clock.uninstall();
|
|
55
|
+
registry.restore();
|
|
56
|
+
env.dispose();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("runs a session with no agent to COMPLETED on the built-in prompt, never reading an instance or an agent", async () => {
|
|
60
|
+
clock.reset();
|
|
61
|
+
const record = deepAgentExecutionRecord({ message: USER_MESSAGE, builtInAssistant: true });
|
|
62
|
+
const systemPrompts: string[] = [];
|
|
63
|
+
const instanceRead = vi.fn(async () => {
|
|
64
|
+
throw new Error("must not be reached");
|
|
65
|
+
});
|
|
66
|
+
const agentRead = vi.fn(async () => {
|
|
67
|
+
throw new Error("must not be reached");
|
|
68
|
+
});
|
|
69
|
+
const scenario = beginDeepAgentScenario({
|
|
70
|
+
env,
|
|
71
|
+
clock,
|
|
72
|
+
record,
|
|
73
|
+
clientOverrides: { getAgentInstance: instanceRead, getAgent: agentRead },
|
|
74
|
+
script: (_tools, context) => {
|
|
75
|
+
systemPrompts.push(context.systemPrompt);
|
|
76
|
+
return { turns: [{ text: ASSISTANT_TEXT, usage: { inputTokens: 900, outputTokens: 20 } }] };
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const invocation = await runDeepAgentTurn(scenario);
|
|
81
|
+
|
|
82
|
+
expect(invocation.outcome.kind).toBe("returned");
|
|
83
|
+
expect(record.persistedPhases).toEqual([
|
|
84
|
+
ExecutionPhase.EXECUTION_IN_PROGRESS,
|
|
85
|
+
ExecutionPhase.EXECUTION_COMPLETED,
|
|
86
|
+
]);
|
|
87
|
+
const final = record.lastFullStatus!;
|
|
88
|
+
const ai = final.messages.filter((m) => m.type === MessageType.MESSAGE_AI);
|
|
89
|
+
expect(ai).toHaveLength(1);
|
|
90
|
+
expect(ai[0].content).toBe(ASSISTANT_TEXT);
|
|
91
|
+
|
|
92
|
+
// The chain stopped at the session: the refusing reads were never
|
|
93
|
+
// reached (they would have failed the resolve phase).
|
|
94
|
+
expect(instanceRead).not.toHaveBeenCalled();
|
|
95
|
+
expect(agentRead).not.toHaveBeenCalled();
|
|
96
|
+
|
|
97
|
+
// The model met the shared words, first.
|
|
98
|
+
expect(systemPrompts).toHaveLength(1);
|
|
99
|
+
expect(systemPrompts[0].startsWith(BUILT_IN_ASSISTANT_INSTRUCTIONS)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* the filter fires, channel templates, referenced files, three input files
|
|
21
21
|
* with a rename and a download URL, the vision disclosure, all five standing
|
|
22
22
|
* sections); the below-threshold skills branch; plan mode; build-from-plan;
|
|
23
|
-
* and the minimal prompt (no instructions, so
|
|
23
|
+
* and the minimal prompt (no instructions, so the built-in assistant's). Plan
|
|
24
24
|
* mode and build-from-plan are mutually exclusive in production, so each has
|
|
25
25
|
* its own golden.
|
|
26
26
|
*
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type StandingSectionKind,
|
|
41
41
|
type VisionPromptInfo,
|
|
42
42
|
} from "../../shared/prompt-sections.js";
|
|
43
|
+
import { effectiveInstructions } from "../../shared/builtin-assistant-prompt.js";
|
|
43
44
|
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
44
45
|
import {
|
|
45
46
|
buildImplementPlanDirective,
|
|
@@ -115,11 +116,8 @@ directly over delegating. Only delegate when context isolation \
|
|
|
115
116
|
or parallelism genuinely helps the user.
|
|
116
117
|
`;
|
|
117
118
|
|
|
118
|
-
/** The agent's instructions when the blueprint carries none; the prompt is never empty (since #1096: was `setup.ts`'s alone). */
|
|
119
|
-
export const DEFAULT_INSTRUCTIONS = "You are a helpful AI assistant.";
|
|
120
|
-
|
|
121
119
|
export interface PromptBuilderInput {
|
|
122
|
-
/** The blueprint's instructions, raw; empty
|
|
120
|
+
/** The blueprint's instructions, raw; empty reads the built-in assistant's (shared/builtin-assistant-prompt.ts). */
|
|
123
121
|
instructions: string;
|
|
124
122
|
provisionResults: ProvisionResult[];
|
|
125
123
|
containerRoot: string;
|
|
@@ -233,7 +231,7 @@ const STANDING_SECTION_HEADINGS: Record<StandingSectionKind, string> = {
|
|
|
233
231
|
* sections. Pure function with no I/O.
|
|
234
232
|
*/
|
|
235
233
|
export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
236
|
-
let prompt = input.instructions
|
|
234
|
+
let prompt = effectiveInstructions(input.instructions);
|
|
237
235
|
|
|
238
236
|
const workspaceSection = buildWorkspacePromptSection(
|
|
239
237
|
input.provisionResults,
|
|
@@ -137,22 +137,26 @@ export async function generateSessionSubject(
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// The agent, when the conversation has one, lends the title its purpose;
|
|
141
|
+
// the built-in assistant (no agent on the session) is titled from the
|
|
142
|
+
// message alone rather than left "Untitled".
|
|
143
|
+
let agentName = "";
|
|
144
|
+
let agentDescription = "";
|
|
140
145
|
const agentId = await resolveAgentId(execution, session, client);
|
|
141
|
-
if (agentId
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
if (agentId !== "") {
|
|
147
|
+
const agent = await getOrSkip(
|
|
148
|
+
() => client.getAgent(agentId),
|
|
149
|
+
`agent not found: ${agentId}`,
|
|
150
|
+
);
|
|
151
|
+
if (agent === undefined) return;
|
|
152
|
+
agentName = agent.metadata?.name ?? "";
|
|
153
|
+
agentDescription = agent.spec?.description ?? "";
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
const agent = await getOrSkip(
|
|
147
|
-
() => client.getAgent(agentId),
|
|
148
|
-
`agent not found: ${agentId}`,
|
|
149
|
-
);
|
|
150
|
-
if (agent === undefined) return;
|
|
151
|
-
|
|
152
156
|
const subject = await generateTitle({
|
|
153
157
|
userMessage,
|
|
154
|
-
agentName
|
|
155
|
-
agentDescription
|
|
158
|
+
agentName,
|
|
159
|
+
agentDescription,
|
|
156
160
|
executionId,
|
|
157
161
|
options,
|
|
158
162
|
});
|
|
@@ -175,7 +179,8 @@ export async function generateSessionSubject(
|
|
|
175
179
|
/**
|
|
176
180
|
* The agent behind the execution: the direct agent_id when the execution
|
|
177
181
|
* carries one, else resolved through the session's agent-instance chain.
|
|
178
|
-
* Empty string when
|
|
182
|
+
* Empty string when there is none (the built-in assistant) or when the
|
|
183
|
+
* instance cannot be read.
|
|
179
184
|
*/
|
|
180
185
|
export async function resolveAgentId(
|
|
181
186
|
execution: AgentExecution,
|
|
@@ -264,7 +269,9 @@ export function buildUserPrompt(
|
|
|
264
269
|
agentDescription: string,
|
|
265
270
|
): string {
|
|
266
271
|
let prompt = `User's first message:\n"${userMessage}"\n\n`;
|
|
267
|
-
|
|
272
|
+
if (agentName !== "") {
|
|
273
|
+
prompt += `Agent: ${agentName}\n`;
|
|
274
|
+
}
|
|
268
275
|
if (agentDescription !== "") {
|
|
269
276
|
prompt += `Agent purpose: ${agentDescription}\n`;
|
|
270
277
|
}
|
|
@@ -759,7 +759,9 @@ export async function resolveMcpServersAndPolicies(
|
|
|
759
759
|
execution.spec?.recalledMemories,
|
|
760
760
|
{
|
|
761
761
|
org: session.metadata?.org ?? "",
|
|
762
|
-
|
|
762
|
+
// Provenance, not scope: empty for the built-in assistant, and the
|
|
763
|
+
// carrier omits an empty field (memory-attachment.ts).
|
|
764
|
+
agentId: blueprint.agent?.metadata?.id ?? "",
|
|
763
765
|
sessionId,
|
|
764
766
|
agentExecutionId: executionId,
|
|
765
767
|
},
|
|
@@ -1,17 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `shared/blueprint-resolver.ts
|
|
2
|
+
* `shared/blueprint-resolver.ts`: `mergeSkillRefs`, the one merge of the
|
|
3
3
|
* agent's and the session's skill refs the turn runtime resolves from
|
|
4
|
-
* (`
|
|
5
|
-
* winning a collision (it may pin a different version)
|
|
4
|
+
* (`resolveBlueprint`) — union, deduplicated by slug, the session's ref
|
|
5
|
+
* winning a collision (it may pin a different version); and
|
|
6
|
+
* `resolveBlueprint`'s two arms: the agent chain walked when the session
|
|
7
|
+
* names an instance, and the built-in assistant when it does not — no
|
|
8
|
+
* instance or agent read, empty instructions, no sub-agents, and the
|
|
9
|
+
* session's own usages and refs as the whole tool set.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
* orchestrator's byte-twin of this function was deleted with its
|
|
9
|
-
* until then the runtime's copy was tested only through the twin.
|
|
11
|
+
* `mergeSkillRefs` carried from `skill-writer.test.ts` in #1096, when the
|
|
12
|
+
* native orchestrator's byte-twin of this function was deleted with its
|
|
13
|
+
* module; until then the runtime's copy was tested only through the twin.
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
|
-
import { describe, it, expect } from "vitest";
|
|
16
|
+
import { describe, it, expect, vi } from "vitest";
|
|
17
|
+
import { create } from "@bufbuild/protobuf";
|
|
18
|
+
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
19
|
+
import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
20
|
+
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
13
21
|
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
14
|
-
import {
|
|
22
|
+
import { mockStigmerClient } from "../../__test-utils__/mock-client.js";
|
|
23
|
+
import { mergeSkillRefs, resolveBlueprint } from "../blueprint-resolver.js";
|
|
24
|
+
|
|
25
|
+
describe("resolveBlueprint", () => {
|
|
26
|
+
it("walks session -> instance -> agent and merges with the session's tools", async () => {
|
|
27
|
+
const client = mockStigmerClient({
|
|
28
|
+
getAgentInstance: vi.fn().mockResolvedValue(
|
|
29
|
+
create(AgentInstanceSchema, { metadata: { id: "agi_1" }, spec: { agentId: "agt_1" } }),
|
|
30
|
+
),
|
|
31
|
+
getAgent: vi.fn().mockResolvedValue(
|
|
32
|
+
create(AgentSchema, {
|
|
33
|
+
metadata: { id: "agt_1", name: "builder" },
|
|
34
|
+
spec: {
|
|
35
|
+
instructions: "You build things.",
|
|
36
|
+
mcpServerUsages: [{ mcpServerRef: { org: "acme", slug: "github" } }],
|
|
37
|
+
skillRefs: [{ org: "acme", slug: "release" }],
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
});
|
|
42
|
+
const session = create(SessionSchema, {
|
|
43
|
+
metadata: { id: "ses_1", org: "acme" },
|
|
44
|
+
spec: {
|
|
45
|
+
agentInstanceId: "agi_1",
|
|
46
|
+
mcpServerUsages: [{ mcpServerRef: { org: "acme", slug: "notes" } }],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const blueprint = await resolveBlueprint(client, session);
|
|
51
|
+
|
|
52
|
+
expect(blueprint.agent?.metadata?.id).toBe("agt_1");
|
|
53
|
+
expect(blueprint.instructions).toBe("You build things.");
|
|
54
|
+
expect(blueprint.mergedMcpServerUsages.map((u) => u.mcpServerRef?.slug).sort()).toEqual(["github", "notes"]);
|
|
55
|
+
expect(blueprint.mergedSkillRefs.map((r) => r.slug)).toEqual(["release"]);
|
|
56
|
+
expect(client.getAgentInstance).toHaveBeenCalledWith("agi_1");
|
|
57
|
+
expect(client.getAgent).toHaveBeenCalledWith("agt_1");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("answers the built-in assistant for a session with no instance: no agent read, the session's tools alone", async () => {
|
|
61
|
+
const client = mockStigmerClient({
|
|
62
|
+
getAgentInstance: vi.fn().mockRejectedValue(new Error("must not be reached")),
|
|
63
|
+
getAgent: vi.fn().mockRejectedValue(new Error("must not be reached")),
|
|
64
|
+
});
|
|
65
|
+
const session = create(SessionSchema, {
|
|
66
|
+
metadata: { id: "ses_2", org: "acme" },
|
|
67
|
+
spec: {
|
|
68
|
+
agentInstanceId: "",
|
|
69
|
+
mcpServerUsages: [{ mcpServerRef: { org: "acme", slug: "notes" } }],
|
|
70
|
+
skillRefs: [{ org: "acme", slug: "writing" }],
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const blueprint = await resolveBlueprint(client, session);
|
|
75
|
+
|
|
76
|
+
expect(blueprint.agent).toBeUndefined();
|
|
77
|
+
expect(blueprint.instructions).toBe("");
|
|
78
|
+
expect(blueprint.subAgents).toEqual([]);
|
|
79
|
+
expect(blueprint.mergedMcpServerUsages.map((u) => u.mcpServerRef?.slug)).toEqual(["notes"]);
|
|
80
|
+
expect(blueprint.mergedSkillRefs.map((r) => r.slug)).toEqual(["writing"]);
|
|
81
|
+
expect(client.getAgentInstance).not.toHaveBeenCalled();
|
|
82
|
+
expect(client.getAgent).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
15
85
|
|
|
16
86
|
function makeRef(slug: string, org = "test-org"): ApiResourceReference {
|
|
17
87
|
return { slug, org, $typeName: "ai.stigmer.commons.apiresource.ApiResourceReference" } as ApiResourceReference;
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* mcp_server_usages. Also merges agent-level and session-level MCP usages
|
|
7
7
|
* and skill refs following the same merge semantics.
|
|
8
8
|
*
|
|
9
|
+
* A session with an EMPTY agent_instance_id is the built-in assistant
|
|
10
|
+
* (session/v1/spec.proto): the chain stops at the session, `agent` is
|
|
11
|
+
* undefined, the instructions are empty (each harness's prompt builder
|
|
12
|
+
* substitutes the one built-in prompt, shared/builtin-assistant-prompt.ts),
|
|
13
|
+
* there are no sub-agents, and the session's own MCP usages and skill refs
|
|
14
|
+
* are the whole tool set — the same merge with an empty agent side, so no
|
|
15
|
+
* consumer downstream needs an agent-less arm of its own.
|
|
16
|
+
*
|
|
9
17
|
* Workspace isolation: resolved workspace directories are validated to
|
|
10
18
|
* ensure they never point at the runner's own app directory. Paths
|
|
11
19
|
* containing runner-internal markers are rejected with a warning.
|
|
@@ -32,8 +40,8 @@ export interface CloudRepo {
|
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
export interface ResolvedBlueprint {
|
|
35
|
-
agent
|
|
36
|
-
|
|
43
|
+
/** The agent the session is bound to; undefined for the built-in assistant. */
|
|
44
|
+
agent: Agent | undefined;
|
|
37
45
|
session: Session;
|
|
38
46
|
sessionSpec: SessionSpec;
|
|
39
47
|
instructions: string;
|
|
@@ -47,7 +55,9 @@ export interface ResolvedBlueprint {
|
|
|
47
55
|
* Resolve the full agent blueprint from the execution chain.
|
|
48
56
|
*
|
|
49
57
|
* Chain: execution.spec.sessionId -> session.spec.agentInstanceId ->
|
|
50
|
-
* agentInstance.spec.agentId -> agent (with full spec)
|
|
58
|
+
* agentInstance.spec.agentId -> agent (with full spec); an empty
|
|
59
|
+
* agent_instance_id ends the chain at the session (the built-in
|
|
60
|
+
* assistant).
|
|
51
61
|
*/
|
|
52
62
|
export async function resolveBlueprint(
|
|
53
63
|
client: StigmerClient,
|
|
@@ -55,18 +65,22 @@ export async function resolveBlueprint(
|
|
|
55
65
|
): Promise<ResolvedBlueprint> {
|
|
56
66
|
const sessionSpec = session.spec!;
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
let agent: Agent | undefined;
|
|
69
|
+
let agentSpec: AgentSpec | undefined;
|
|
70
|
+
if (sessionSpec.agentInstanceId !== "") {
|
|
71
|
+
const agentInstance = await client.getAgentInstance(sessionSpec.agentInstanceId);
|
|
72
|
+
const agentId = agentInstance.spec!.agentId;
|
|
73
|
+
agent = await client.getAgent(agentId);
|
|
74
|
+
agentSpec = agent.spec!;
|
|
75
|
+
}
|
|
62
76
|
|
|
63
77
|
const mergedMcpServerUsages = mergeMcpServerUsages(
|
|
64
|
-
agentSpec
|
|
78
|
+
agentSpec?.mcpServerUsages ?? [],
|
|
65
79
|
sessionSpec.mcpServerUsages,
|
|
66
80
|
);
|
|
67
81
|
|
|
68
82
|
const mergedSkillRefs = mergeSkillRefs(
|
|
69
|
-
agentSpec
|
|
83
|
+
agentSpec?.skillRefs ?? [],
|
|
70
84
|
sessionSpec.skillRefs,
|
|
71
85
|
);
|
|
72
86
|
|
|
@@ -74,11 +88,10 @@ export async function resolveBlueprint(
|
|
|
74
88
|
|
|
75
89
|
return {
|
|
76
90
|
agent,
|
|
77
|
-
agentSpec,
|
|
78
91
|
session,
|
|
79
92
|
sessionSpec,
|
|
80
|
-
instructions: agentSpec
|
|
81
|
-
subAgents: agentSpec
|
|
93
|
+
instructions: agentSpec?.instructions ?? "",
|
|
94
|
+
subAgents: agentSpec?.subAgents ?? [],
|
|
82
95
|
mergedMcpServerUsages,
|
|
83
96
|
mergedSkillRefs,
|
|
84
97
|
cloudRepos,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in assistant's instructions: the ONE text a run reads when its
|
|
3
|
+
* blueprint carries no instructions — because the session names no agent
|
|
4
|
+
* (session/v1/spec.proto, an empty agent_instance_id) or because the agent
|
|
5
|
+
* it names left its instructions empty. Both harnesses' prompt builders
|
|
6
|
+
* substitute these words on that arm and frame them their own way (the
|
|
7
|
+
* `prompt-sections.ts` doctrine: the framing is house style, the words are
|
|
8
|
+
* shared), so a person who picks no agent meets the same assistant on the
|
|
9
|
+
* native and the Cursor harness, and a maintainer changes the assistant in
|
|
10
|
+
* one place.
|
|
11
|
+
*
|
|
12
|
+
* A prompt is code: versioned here, pinned by the prompt goldens of both
|
|
13
|
+
* harnesses (`execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md`,
|
|
14
|
+
* `execute-cursor/__tests__/goldens/*no-instructions*`). There is no stored
|
|
15
|
+
* "default agent" row anywhere whose instructions could drift from these.
|
|
16
|
+
*
|
|
17
|
+
* What the words leave to the builders on purpose: the response rules, the
|
|
18
|
+
* sub-agent rules, the skills protocol and every other section quote an
|
|
19
|
+
* engine's tools and stay per harness. These words say who the assistant
|
|
20
|
+
* is and how it works with a person; nothing here names a tool.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** The display name every surface uses for the built-in assistant. */
|
|
24
|
+
export const BUILT_IN_ASSISTANT_NAME = "Assistant";
|
|
25
|
+
|
|
26
|
+
export const BUILT_IN_ASSISTANT_INSTRUCTIONS = [
|
|
27
|
+
"You are Stigmer's assistant. Help with whatever the person brings: answer, reason, write and edit, and use the tools and skills attached to this conversation when they help.",
|
|
28
|
+
"Be direct and concrete. Say what you do not know. Ask when a request is ambiguous. Prefer changing what exists over creating anew.",
|
|
29
|
+
].join("\n");
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The instructions a run reads: the blueprint's own when it has any, the
|
|
33
|
+
* built-in assistant's otherwise. Both prompt builders call this on their
|
|
34
|
+
* instructions arm so neither carries a second copy of the fallback.
|
|
35
|
+
*/
|
|
36
|
+
export function effectiveInstructions(instructions: string): string {
|
|
37
|
+
return instructions === "" ? BUILT_IN_ASSISTANT_INSTRUCTIONS : instructions;
|
|
38
|
+
}
|