@selesai/code 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +23 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +100 -5
- package/dist/config.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +6 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/defaults/settings.json +2 -4
- package/dist/extensions/pi-intercom/LICENSE +21 -0
- package/dist/extensions/pi-intercom/README.md +484 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +345 -0
- package/dist/extensions/pi-intercom/broker/client.ts +535 -0
- package/dist/extensions/pi-intercom/broker/framing.ts +57 -0
- package/dist/extensions/pi-intercom/broker/paths.ts +20 -0
- package/dist/extensions/pi-intercom/broker/spawn.ts +307 -0
- package/dist/extensions/pi-intercom/config.ts +108 -0
- package/dist/extensions/pi-intercom/index.ts +1778 -0
- package/dist/extensions/pi-intercom/package.json +38 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +102 -0
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +512 -0
- package/dist/extensions/pi-intercom/types.ts +46 -0
- package/dist/extensions/pi-intercom/ui/compose.ts +139 -0
- package/dist/extensions/pi-intercom/ui/inline-message.ts +76 -0
- package/dist/extensions/pi-intercom/ui/session-list.ts +162 -0
- package/dist/extensions/pi-rewind-hook/README.md +245 -0
- package/dist/extensions/pi-rewind-hook/index.ts +1445 -0
- package/dist/extensions/pi-rewind-hook/package.json +29 -0
- package/dist/extensions/pi-subagents/README.md +1290 -0
- package/dist/extensions/pi-subagents/agents/context-builder.md +46 -0
- package/dist/extensions/pi-subagents/agents/delegate.md +12 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +73 -0
- package/dist/extensions/pi-subagents/agents/planner.md +55 -0
- package/dist/extensions/pi-subagents/agents/researcher.md +52 -0
- package/dist/extensions/pi-subagents/agents/reviewer.md +79 -0
- package/dist/extensions/pi-subagents/agents/scout.md +50 -0
- package/dist/extensions/pi-subagents/agents/worker.md +55 -0
- package/dist/extensions/pi-subagents/package.json +83 -0
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +13 -0
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +59 -0
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +55 -0
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +61 -0
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +50 -0
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +54 -0
- package/dist/extensions/pi-subagents/prompts/review-loop.md +41 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +855 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +875 -0
- package/dist/extensions/pi-subagents/src/agents/agent-scope.ts +6 -0
- package/dist/extensions/pi-subagents/src/agents/agent-selection.ts +25 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +110 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +1379 -0
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +252 -0
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +93 -0
- package/dist/extensions/pi-subagents/src/agents/identity.ts +30 -0
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +191 -0
- package/dist/extensions/pi-subagents/src/agents/skills.ts +729 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +16 -0
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +92 -0
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +220 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +171 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +627 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +265 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +412 -0
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +377 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +637 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +916 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +355 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +391 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +338 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +282 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +161 -0
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +63 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +177 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +108 -0
- package/dist/extensions/pi-subagents/src/runs/background/parallel-groups.ts +45 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +316 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +273 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +341 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +2458 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +13 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +1333 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +1217 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +1092 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +3125 -0
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +856 -0
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +116 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +147 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +295 -0
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +175 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +141 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +819 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +115 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +149 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +240 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +115 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +60 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +77 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +223 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +222 -0
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +206 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +577 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +98 -0
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +73 -0
- package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +40 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +76 -0
- package/dist/extensions/pi-subagents/src/shared/formatters.ts +133 -0
- package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +81 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +78 -0
- package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +85 -0
- package/dist/extensions/pi-subagents/src/shared/session-identity.ts +10 -0
- package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +44 -0
- package/dist/extensions/pi-subagents/src/shared/settings.ts +448 -0
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +49 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +1064 -0
- package/dist/extensions/pi-subagents/src/shared/utils.ts +469 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +420 -0
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +176 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1165 -0
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +292 -0
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +80 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +1728 -0
- package/dist/extensions/quick.ts +630 -0
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +4 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeBox schemas for subagent tool parameters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { SUBAGENT_ACTIONS } from "../shared/types.ts";
|
|
7
|
+
|
|
8
|
+
function keepTopLevelParameterDescriptions<T>(schema: T): T {
|
|
9
|
+
return pruneNestedDescriptions(schema, []) as T;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function pruneNestedDescriptions(value: unknown, path: string[]): unknown {
|
|
13
|
+
if (!value || typeof value !== "object") return value;
|
|
14
|
+
|
|
15
|
+
const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
16
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
17
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
18
|
+
if (!descriptor) continue;
|
|
19
|
+
if (key === "description" && !isTopLevelParameterDescription(path)) continue;
|
|
20
|
+
if ("value" in descriptor) {
|
|
21
|
+
const nextPath = typeof key === "string" ? [...path, key] : path;
|
|
22
|
+
descriptor.value = pruneNestedDescriptions(descriptor.value, nextPath);
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(result, key, descriptor);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isTopLevelParameterDescription(path: string[]): boolean {
|
|
30
|
+
return path.length === 2 && path[0] === "properties";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SkillOverride = Type.Unsafe({
|
|
34
|
+
anyOf: [
|
|
35
|
+
{ type: "array", items: { type: "string" } },
|
|
36
|
+
{ type: "boolean" },
|
|
37
|
+
{ type: "string" },
|
|
38
|
+
],
|
|
39
|
+
description: "Skill name(s) to make available (comma-separated), array of strings, or boolean (false disables, true uses default)",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const OutputOverride = Type.Unsafe({
|
|
43
|
+
anyOf: [
|
|
44
|
+
{ type: "string" },
|
|
45
|
+
{ type: "boolean" },
|
|
46
|
+
],
|
|
47
|
+
description: "Output filename/path (string), or false to disable file output",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const OutputModeOverride = Type.String({
|
|
51
|
+
enum: ["inline", "file-only"],
|
|
52
|
+
description: "Return saved output inline (default) or only a concise file reference. file-only requires output to be a path.",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const ReadsOverride = Type.Unsafe({
|
|
56
|
+
anyOf: [
|
|
57
|
+
{ type: "array", items: { type: "string" } },
|
|
58
|
+
{ type: "boolean" },
|
|
59
|
+
],
|
|
60
|
+
description: "Files to read before running (array of filenames), or false to disable",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const JsonSchemaObject = Type.Unsafe({
|
|
64
|
+
type: "object",
|
|
65
|
+
additionalProperties: true,
|
|
66
|
+
description: "JSON Schema object for strict structured output. Non-object roots are rejected.",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const AcceptanceOverride = Type.Unsafe({
|
|
70
|
+
anyOf: [
|
|
71
|
+
{ type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
72
|
+
{ type: "boolean", enum: [false] },
|
|
73
|
+
{ type: "object", additionalProperties: true },
|
|
74
|
+
],
|
|
75
|
+
description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands.",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const TaskItem = Type.Object({
|
|
79
|
+
agent: Type.String(),
|
|
80
|
+
task: Type.String(),
|
|
81
|
+
cwd: Type.Optional(Type.String()),
|
|
82
|
+
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
|
|
83
|
+
output: Type.Optional(OutputOverride),
|
|
84
|
+
outputMode: Type.Optional(OutputModeOverride),
|
|
85
|
+
reads: Type.Optional(ReadsOverride),
|
|
86
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking for this task" })),
|
|
87
|
+
model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
|
|
88
|
+
skill: Type.Optional(SkillOverride),
|
|
89
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Parallel task item (within a parallel step)
|
|
93
|
+
const ParallelTaskSchema = Type.Object({
|
|
94
|
+
agent: Type.String(),
|
|
95
|
+
task: Type.Optional(Type.String({ description: "Task template with {task}, {previous}, {chain_dir} variables. Defaults to {previous}." })),
|
|
96
|
+
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
97
|
+
label: Type.Optional(Type.String({ description: "Optional user-facing label for this parallel task." })),
|
|
98
|
+
as: Type.Optional(Type.String({ description: "Optional safe identifier used as {outputs.name} in later chain steps." })),
|
|
99
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
100
|
+
cwd: Type.Optional(Type.String()),
|
|
101
|
+
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
|
|
102
|
+
output: Type.Optional(OutputOverride),
|
|
103
|
+
outputMode: Type.Optional(OutputModeOverride),
|
|
104
|
+
reads: Type.Optional(ReadsOverride),
|
|
105
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
106
|
+
skill: Type.Optional(SkillOverride),
|
|
107
|
+
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
108
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const DynamicExpandSchema = Type.Object({
|
|
112
|
+
from: Type.Object({
|
|
113
|
+
output: Type.String({ description: "Prior named structured output to expand from." }),
|
|
114
|
+
path: Type.String({ description: "JSON Pointer into the structured output, e.g. /items." }),
|
|
115
|
+
}, { additionalProperties: false }),
|
|
116
|
+
item: Type.Optional(Type.String({ description: "Template variable name for each item. Defaults to item." })),
|
|
117
|
+
key: Type.Optional(Type.String({ description: "JSON Pointer relative to each item for stable child ids." })),
|
|
118
|
+
maxItems: Type.Optional(Type.Integer({ minimum: 0, description: "Required fanout bound unless configured globally." })),
|
|
119
|
+
onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "Empty input behavior. Defaults to skip." })),
|
|
120
|
+
}, { additionalProperties: false });
|
|
121
|
+
|
|
122
|
+
const DynamicParallelTemplateSchema = Type.Object({
|
|
123
|
+
agent: Type.String(),
|
|
124
|
+
task: Type.Optional(Type.String({ description: "Task template with {item}, {item.path}, {task}, {previous}, {chain_dir}, and {outputs.name} variables." })),
|
|
125
|
+
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
126
|
+
label: Type.Optional(Type.String({ description: "Optional user-facing label; item templates are supported." })),
|
|
127
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
128
|
+
cwd: Type.Optional(Type.String()),
|
|
129
|
+
output: Type.Optional(OutputOverride),
|
|
130
|
+
outputMode: Type.Optional(OutputModeOverride),
|
|
131
|
+
reads: Type.Optional(ReadsOverride),
|
|
132
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
133
|
+
skill: Type.Optional(SkillOverride),
|
|
134
|
+
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
135
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
136
|
+
}, { additionalProperties: false });
|
|
137
|
+
|
|
138
|
+
const DynamicCollectSchema = Type.Object({
|
|
139
|
+
as: Type.String({ description: "Safe output name for the ordered collected result array." }),
|
|
140
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
141
|
+
}, { additionalProperties: false });
|
|
142
|
+
|
|
143
|
+
// Flattened so chain steps do not need an object-shape anyOf/oneOf union.
|
|
144
|
+
const ChainItem = Type.Object({
|
|
145
|
+
agent: Type.Optional(Type.String({ description: "Sequential step agent name" })),
|
|
146
|
+
task: Type.Optional(Type.String({
|
|
147
|
+
description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder, {outputs.name}=prior named output. Required for first step, defaults to '{previous}' for subsequent steps."
|
|
148
|
+
})),
|
|
149
|
+
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
150
|
+
label: Type.Optional(Type.String({ description: "Optional user-facing label for this chain step." })),
|
|
151
|
+
as: Type.Optional(Type.String({ description: "Optional safe identifier used as {outputs.name} in later chain steps." })),
|
|
152
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
153
|
+
cwd: Type.Optional(Type.String()),
|
|
154
|
+
output: Type.Optional(OutputOverride),
|
|
155
|
+
outputMode: Type.Optional(OutputModeOverride),
|
|
156
|
+
reads: Type.Optional(ReadsOverride),
|
|
157
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
158
|
+
skill: Type.Optional(SkillOverride),
|
|
159
|
+
model: Type.Optional(Type.String({ description: "Override model for this step" })),
|
|
160
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
161
|
+
parallel: Type.Optional(Type.Unsafe({
|
|
162
|
+
anyOf: [
|
|
163
|
+
Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
|
|
164
|
+
DynamicParallelTemplateSchema,
|
|
165
|
+
],
|
|
166
|
+
description: "Static parallel tasks array, or a single dynamic fanout child template when expand/collect are present.",
|
|
167
|
+
})),
|
|
168
|
+
expand: Type.Optional(DynamicExpandSchema),
|
|
169
|
+
collect: Type.Optional(DynamicCollectSchema),
|
|
170
|
+
concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
|
|
171
|
+
failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
|
|
172
|
+
worktree: Type.Optional(Type.Boolean({
|
|
173
|
+
description: "Create isolated git worktrees for each parallel task."
|
|
174
|
+
})),
|
|
175
|
+
}, {
|
|
176
|
+
description: "Chain step: use {agent, task?, ...} for sequential, {parallel: [...]} for static concurrent execution, or {expand, parallel: {...}, collect} for dynamic fanout.",
|
|
177
|
+
additionalProperties: false,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const ControlOverrides = Type.Object({
|
|
181
|
+
enabled: Type.Optional(Type.Boolean({ description: "Enable/disable subagent control attention tracking for this run" })),
|
|
182
|
+
needsAttentionAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "No-observed-activity window before a run needs attention" })),
|
|
183
|
+
activeNoticeAfterMs: Type.Optional(Type.Integer({ minimum: 1, description: "Active-long-running notice threshold by elapsed ms (default: 240000)" })),
|
|
184
|
+
activeNoticeAfterTurns: Type.Optional(Type.Integer({ minimum: 1, description: "Optional active-long-running notice threshold by assistant turns (disabled by default)" })),
|
|
185
|
+
activeNoticeAfterTokens: Type.Optional(Type.Integer({ minimum: 1, description: "Optional active-long-running notice threshold by total tokens (disabled by default)" })),
|
|
186
|
+
failedToolAttemptsBeforeAttention: Type.Optional(Type.Integer({ minimum: 1, description: "Consecutive mutating-tool failures before escalating to needs_attention (default: 3)" })),
|
|
187
|
+
notifyOn: Type.Optional(Type.Array(Type.String({ enum: ["active_long_running", "needs_attention"] }), {
|
|
188
|
+
description: "Control event types that should notify the parent/orchestrator. Defaults to active_long_running and needs_attention.",
|
|
189
|
+
})),
|
|
190
|
+
notifyChannels: Type.Optional(Type.Array(Type.String({ enum: ["event", "async", "intercom"] }), {
|
|
191
|
+
description: "Notification channels to use when available. Defaults to event, async, and intercom.",
|
|
192
|
+
})),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const SubagentParamsSchema = Type.Object({
|
|
196
|
+
agent: Type.Optional(Type.String({ description: "Agent name (SINGLE mode) or target for management get/update/delete" })),
|
|
197
|
+
task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
|
|
198
|
+
// Management action (when present, tool operates in management mode)
|
|
199
|
+
action: Type.Optional(Type.String({
|
|
200
|
+
enum: [...SUBAGENT_ACTIONS],
|
|
201
|
+
description: "Management/control action. Omit for execution mode."
|
|
202
|
+
})),
|
|
203
|
+
id: Type.Optional(Type.String({
|
|
204
|
+
description: "Run id or prefix for action='status', action='interrupt', action='resume', or action='append-step'."
|
|
205
|
+
})),
|
|
206
|
+
runId: Type.Optional(Type.String({
|
|
207
|
+
description: "Target run ID for action='interrupt', action='resume', or action='append-step'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls."
|
|
208
|
+
})),
|
|
209
|
+
dir: Type.Optional(Type.String({
|
|
210
|
+
description: "Async run directory for action='status' or action='resume'."
|
|
211
|
+
})),
|
|
212
|
+
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child." })),
|
|
213
|
+
message: Type.Optional(Type.String({ description: "Follow-up message for action='resume'. Use index to choose a child from multi-child runs." })),
|
|
214
|
+
// Chain identifier for management (can't reuse 'chain' — that's the execution array)
|
|
215
|
+
chainName: Type.Optional(Type.String({
|
|
216
|
+
description: "Chain name for get/update/delete management actions"
|
|
217
|
+
})),
|
|
218
|
+
// Agent/chain configuration for create/update (nested to avoid conflicts with execution fields)
|
|
219
|
+
config: Type.Optional(Type.Unsafe({
|
|
220
|
+
anyOf: [
|
|
221
|
+
{ type: "object", additionalProperties: true },
|
|
222
|
+
{ type: "string" },
|
|
223
|
+
],
|
|
224
|
+
description: "Agent/chain config for create/update. Object or JSON string; presence of steps creates a chain."
|
|
225
|
+
})),
|
|
226
|
+
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, outputMode?, reads?, progress?}, ...]" })),
|
|
227
|
+
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })),
|
|
228
|
+
worktree: Type.Optional(Type.Boolean({
|
|
229
|
+
description: "Create isolated git worktrees for parallel tasks; requires clean git state."
|
|
230
|
+
})),
|
|
231
|
+
chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN mode: sequential steps; each result becomes {previous}. append-step takes one tail step and may use {chain_dir}/{outputs.name}." })),
|
|
232
|
+
context: Type.Optional(Type.String({
|
|
233
|
+
enum: ["fresh", "fork"],
|
|
234
|
+
description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without defaultContext: 'fork' run fresh.",
|
|
235
|
+
})),
|
|
236
|
+
chainDir: Type.Optional(Type.String({ description: "Persistent chain artifact directory; defaults to user-scoped temp storage." })),
|
|
237
|
+
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
238
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Foreground timeout ms; alias of maxRuntimeMs." })),
|
|
239
|
+
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias of timeoutMs for foreground timeout." })),
|
|
240
|
+
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
|
|
241
|
+
cwd: Type.Optional(Type.String()),
|
|
242
|
+
artifacts: Type.Optional(Type.Boolean({ description: "Write debug artifacts (default: true)" })),
|
|
243
|
+
includeProgress: Type.Optional(Type.Boolean({ description: "Include full progress in result (default: false)" })),
|
|
244
|
+
share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist for sharing (default: false)" })),
|
|
245
|
+
sessionDir: Type.Optional(
|
|
246
|
+
Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
|
|
247
|
+
),
|
|
248
|
+
// Clarification TUI
|
|
249
|
+
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution. Explicit clarify: true keeps the run foreground for the clarify UI; omitted clarify can still run in the background when async: true is set." })),
|
|
250
|
+
control: Type.Optional(ControlOverrides),
|
|
251
|
+
// Solo agent overrides
|
|
252
|
+
output: Type.Optional(Type.Unsafe({
|
|
253
|
+
anyOf: [
|
|
254
|
+
{ type: "string" },
|
|
255
|
+
{ type: "boolean" },
|
|
256
|
+
],
|
|
257
|
+
description: "Output file for single agent (string), or false to disable. Relative paths resolve against cwd.",
|
|
258
|
+
})),
|
|
259
|
+
outputMode: Type.Optional(OutputModeOverride),
|
|
260
|
+
skill: Type.Optional(SkillOverride),
|
|
261
|
+
model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
|
|
262
|
+
acceptance: Type.Optional(AcceptanceOverride),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export const SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema);
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import type { AgentConfig } from "../agents/agents.ts";
|
|
6
|
+
import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "../shared/types.ts";
|
|
7
|
+
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
8
|
+
|
|
9
|
+
const PI_INTERCOM_PACKAGE_NAME = "pi-intercom";
|
|
10
|
+
|
|
11
|
+
function defaultAgentDir(): string {
|
|
12
|
+
return getAgentDir();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function defaultIntercomExtensionDir(agentDir = defaultAgentDir()): string {
|
|
16
|
+
return path.join(agentDir, "extensions", PI_INTERCOM_PACKAGE_NAME);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const INTERCOM_EXTENSION_DIR_ENV = "PI_INTERCOM_EXTENSION_DIR";
|
|
20
|
+
|
|
21
|
+
// Launcher-provided override for the pi-intercom package directory. Lets a hermetic
|
|
22
|
+
// wrapper point the subagent intercom bridge at a read-only install (e.g. a Nix-store
|
|
23
|
+
// path) instead of seeding the package into the writable agent dir.
|
|
24
|
+
function envIntercomExtensionDir(): string | undefined {
|
|
25
|
+
const dir = process.env[INTERCOM_EXTENSION_DIR_ENV]?.trim();
|
|
26
|
+
return dir ? dir : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function defaultIntercomConfigPath(agentDir = defaultAgentDir()): string {
|
|
30
|
+
return path.join(agentDir, "intercom", "config.json");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function defaultSubagentConfigDir(agentDir = defaultAgentDir()): string {
|
|
34
|
+
return path.join(agentDir, "extensions", "subagent");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat";
|
|
38
|
+
export const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
39
|
+
const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only. Do not continue that conversation or send questions, status updates, or completion handoffs to the supervisor in normal assistant text.
|
|
40
|
+
|
|
41
|
+
Use contact_supervisor first. It resolves the supervisor session "{orchestratorTarget}" and run metadata automatically.
|
|
42
|
+
- Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "<question>" })
|
|
43
|
+
- After contact_supervisor with reason "need_decision", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
|
|
44
|
+
- Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. Review-only/no-edit wins; leave files unchanged and mention the conflict in your final result only if it matters.
|
|
45
|
+
- Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
|
|
46
|
+
- Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
47
|
+
|
|
48
|
+
Do not use contact_supervisor or intercom for routine completion handoffs. If no coordination is needed, return a focused task result.`;
|
|
49
|
+
|
|
50
|
+
export interface IntercomBridgeState {
|
|
51
|
+
active: boolean;
|
|
52
|
+
mode: IntercomBridgeMode;
|
|
53
|
+
orchestratorTarget?: string;
|
|
54
|
+
extensionDir: string;
|
|
55
|
+
instruction: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface IntercomBridgeDiagnostic {
|
|
59
|
+
active: boolean;
|
|
60
|
+
mode: IntercomBridgeMode;
|
|
61
|
+
wantsIntercom: boolean;
|
|
62
|
+
piIntercomAvailable: boolean;
|
|
63
|
+
extensionDir: string;
|
|
64
|
+
configPath?: string;
|
|
65
|
+
orchestratorTarget?: string;
|
|
66
|
+
reason?: string;
|
|
67
|
+
intercomConfigEnabled?: boolean;
|
|
68
|
+
intercomConfigError?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ResolveIntercomBridgeInput {
|
|
72
|
+
config: ExtensionConfig["intercomBridge"];
|
|
73
|
+
context: "fresh" | "fork" | undefined;
|
|
74
|
+
orchestratorTarget?: string;
|
|
75
|
+
extensionDir?: string;
|
|
76
|
+
configPath?: string;
|
|
77
|
+
settingsDir?: string;
|
|
78
|
+
cwd?: string;
|
|
79
|
+
agentDir?: string;
|
|
80
|
+
globalNpmRoot?: string | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resolveIntercomSessionTarget(sessionName: string | undefined, sessionId: string): string {
|
|
84
|
+
const trimmedName = sessionName?.trim();
|
|
85
|
+
if (trimmedName) return trimmedName;
|
|
86
|
+
const normalizedSessionId = sessionId.startsWith("session-") ? sessionId.slice("session-".length) : sessionId;
|
|
87
|
+
return `${DEFAULT_INTERCOM_TARGET_PREFIX}-${normalizedSessionId.slice(0, 8)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function sanitizeIntercomTargetPart(value: string): string {
|
|
91
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolveSubagentIntercomTarget(runId: string, agent: string, index?: number): string {
|
|
95
|
+
const stepSuffix = index !== undefined ? `-${index + 1}` : "";
|
|
96
|
+
return `subagent-${sanitizeIntercomTargetPart(agent)}-${sanitizeIntercomTargetPart(runId)}${stepSuffix}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveIntercomBridgeMode(value: unknown): IntercomBridgeMode {
|
|
100
|
+
if (value === "off" || value === "always" || value === "fork-only") return value;
|
|
101
|
+
return "always";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveIntercomBridgeConfig(value: ExtensionConfig["intercomBridge"]): Required<IntercomBridgeConfig> {
|
|
105
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
106
|
+
return {
|
|
107
|
+
mode: "always",
|
|
108
|
+
instructionFile: "",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
mode: resolveIntercomBridgeMode(value.mode),
|
|
113
|
+
instructionFile: typeof value.instructionFile === "string" ? value.instructionFile : "",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function intercomConfigStatus(configPath: string): { enabled: boolean; error?: unknown } {
|
|
118
|
+
if (!fs.existsSync(configPath)) return { enabled: true };
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8")) as { enabled?: unknown };
|
|
121
|
+
return { enabled: parsed.enabled !== false };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return { enabled: true, error };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function readJsonBestEffort(filePath: string): unknown {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const code = error && typeof error === "object" && "code" in error ? (error as NodeJS.ErrnoException).code : undefined;
|
|
132
|
+
if (code !== "ENOENT") console.warn(`Failed to read JSON from '${filePath}'.`, error);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function packageHasPiExtension(packageRoot: string): boolean {
|
|
138
|
+
if (!fs.existsSync(packageRoot)) return false;
|
|
139
|
+
const pkg = readJsonBestEffort(path.join(packageRoot, "package.json"));
|
|
140
|
+
if (pkg && typeof pkg === "object" && !Array.isArray(pkg)) {
|
|
141
|
+
const pi = (pkg as { pi?: unknown }).pi;
|
|
142
|
+
if (pi && typeof pi === "object" && !Array.isArray(pi)) {
|
|
143
|
+
const extensions = (pi as { extensions?: unknown }).extensions;
|
|
144
|
+
return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim() !== "");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return fs.existsSync(path.join(packageRoot, "extensions"));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isSafePackagePath(value: string): boolean {
|
|
151
|
+
return value.length > 0
|
|
152
|
+
&& !path.isAbsolute(value)
|
|
153
|
+
&& value.split(/[\\/]/).every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseNpmPackageName(source: string): string | undefined {
|
|
157
|
+
const spec = source.slice(4).trim();
|
|
158
|
+
if (!spec) return undefined;
|
|
159
|
+
const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/);
|
|
160
|
+
const packageName = match?.[1] ?? spec;
|
|
161
|
+
return isSafePackagePath(packageName) ? packageName : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function packageEntrySource(entry: unknown): string | undefined {
|
|
165
|
+
if (typeof entry === "string") return entry;
|
|
166
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry) && typeof (entry as { source?: unknown }).source === "string") {
|
|
167
|
+
return (entry as { source: string }).source;
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function packageEntryAllowsExtensions(entry: unknown): boolean {
|
|
173
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return true;
|
|
174
|
+
const extensions = (entry as { extensions?: unknown }).extensions;
|
|
175
|
+
return !Array.isArray(extensions) || extensions.length > 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function findNearestProjectConfigDir(cwd: string): string | undefined {
|
|
179
|
+
let current = path.resolve(cwd);
|
|
180
|
+
while (true) {
|
|
181
|
+
const configDir = getProjectConfigDir(current);
|
|
182
|
+
if (fs.existsSync(path.join(configDir, "settings.json"))) return configDir;
|
|
183
|
+
const parent = path.dirname(current);
|
|
184
|
+
if (parent === current) return undefined;
|
|
185
|
+
current = parent;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let cachedGlobalNpmRoot: string | null | undefined;
|
|
190
|
+
|
|
191
|
+
function getGlobalNpmRoot(): string | null {
|
|
192
|
+
if (cachedGlobalNpmRoot !== undefined) return cachedGlobalNpmRoot;
|
|
193
|
+
try {
|
|
194
|
+
cachedGlobalNpmRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
195
|
+
return cachedGlobalNpmRoot;
|
|
196
|
+
} catch {
|
|
197
|
+
cachedGlobalNpmRoot = null;
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function tmpNpmIntercomPackageDir(agentDir: string): string | undefined {
|
|
203
|
+
const tmpNpmDir = path.join(agentDir, "tmp", "extensions", "npm");
|
|
204
|
+
try {
|
|
205
|
+
const entries = fs.readdirSync(tmpNpmDir, { withFileTypes: true });
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
if (!entry.isDirectory()) continue;
|
|
208
|
+
const pkgDir = path.join(tmpNpmDir, entry.name, "node_modules", PI_INTERCOM_PACKAGE_NAME);
|
|
209
|
+
if (fs.existsSync(pkgDir) && packageHasPiExtension(pkgDir)) {
|
|
210
|
+
return path.resolve(pkgDir);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// ignore ENOTDIR, ENOENT, permission errors
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function configuredPiIntercomPackageDir(input: ResolveIntercomBridgeInput, agentDir: string): string | undefined {
|
|
220
|
+
const projectConfigDir = input.cwd ? findNearestProjectConfigDir(path.resolve(input.cwd)) : undefined;
|
|
221
|
+
const settingsFiles = [
|
|
222
|
+
...(projectConfigDir ? [{ file: path.join(projectConfigDir, "settings.json"), configDir: projectConfigDir, scope: "project" as const }] : []),
|
|
223
|
+
{ file: path.join(agentDir, "settings.json"), configDir: agentDir, scope: "user" as const },
|
|
224
|
+
];
|
|
225
|
+
const globalNpmRoot = input.globalNpmRoot === undefined ? getGlobalNpmRoot() : input.globalNpmRoot;
|
|
226
|
+
|
|
227
|
+
for (const { file, configDir, scope } of settingsFiles) {
|
|
228
|
+
const settings = readJsonBestEffort(file);
|
|
229
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) continue;
|
|
230
|
+
const packages = (settings as { packages?: unknown }).packages;
|
|
231
|
+
if (!Array.isArray(packages)) continue;
|
|
232
|
+
|
|
233
|
+
for (const entry of packages) {
|
|
234
|
+
if (!packageEntryAllowsExtensions(entry)) continue;
|
|
235
|
+
const source = packageEntrySource(entry)?.trim();
|
|
236
|
+
if (!source?.startsWith("npm:")) continue;
|
|
237
|
+
const packageName = parseNpmPackageName(source);
|
|
238
|
+
if (packageName !== PI_INTERCOM_PACKAGE_NAME) continue;
|
|
239
|
+
const candidates = scope === "project"
|
|
240
|
+
? [path.join(configDir, "npm", "node_modules", packageName)]
|
|
241
|
+
: [
|
|
242
|
+
...(globalNpmRoot ? [path.join(globalNpmRoot, packageName)] : []),
|
|
243
|
+
path.join(agentDir, "npm", "node_modules", packageName),
|
|
244
|
+
];
|
|
245
|
+
const packageRoot = candidates.find(packageHasPiExtension);
|
|
246
|
+
if (packageRoot) return path.resolve(packageRoot);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function resolveIntercomExtensionDir(input: ResolveIntercomBridgeInput, agentDir: string): string {
|
|
253
|
+
const legacyDir = path.resolve(input.extensionDir ?? envIntercomExtensionDir() ?? defaultIntercomExtensionDir(agentDir));
|
|
254
|
+
if (fs.existsSync(legacyDir)) return legacyDir;
|
|
255
|
+
|
|
256
|
+
const configured = configuredPiIntercomPackageDir(input, agentDir);
|
|
257
|
+
if (configured) return configured;
|
|
258
|
+
|
|
259
|
+
const tmpDir = tmpNpmIntercomPackageDir(agentDir);
|
|
260
|
+
if (tmpDir) return tmpDir;
|
|
261
|
+
|
|
262
|
+
return legacyDir;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function extensionSandboxAllowsIntercom(extensions: string[] | undefined, extensionDir: string): boolean {
|
|
266
|
+
if (extensions === undefined) return true;
|
|
267
|
+
|
|
268
|
+
const intercomDir = path.resolve(extensionDir).replaceAll("\\", "/").toLowerCase();
|
|
269
|
+
for (const entry of extensions) {
|
|
270
|
+
const normalized = entry.trim().replaceAll("\\", "/").toLowerCase();
|
|
271
|
+
if (normalized === "pi-intercom") return true;
|
|
272
|
+
if (normalized === intercomDir) return true;
|
|
273
|
+
if (normalized.startsWith(`${intercomDir}/`)) return true;
|
|
274
|
+
if (normalized.endsWith("/pi-intercom")) return true;
|
|
275
|
+
if (normalized.includes("/pi-intercom/")) return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function expandTilde(filePath: string): string {
|
|
281
|
+
return filePath.startsWith("~/") ? path.join(os.homedir(), filePath.slice(2)) : filePath;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveInstructionTemplate(instructionFile: string, settingsDir: string): string {
|
|
285
|
+
if (!instructionFile) return DEFAULT_INTERCOM_BRIDGE_TEMPLATE;
|
|
286
|
+
const expandedPath = expandTilde(instructionFile);
|
|
287
|
+
const resolvedPath = path.isAbsolute(expandedPath)
|
|
288
|
+
? expandedPath
|
|
289
|
+
: path.resolve(settingsDir, expandedPath);
|
|
290
|
+
try {
|
|
291
|
+
return fs.readFileSync(resolvedPath, "utf-8");
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.warn(`Failed to read intercom bridge instructionFile at '${resolvedPath}'. Using default instructions.`, error);
|
|
294
|
+
return DEFAULT_INTERCOM_BRIDGE_TEMPLATE;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function buildIntercomBridgeInstruction(orchestratorTarget: string, template: string): string {
|
|
299
|
+
const instruction = template.replaceAll("{orchestratorTarget}", orchestratorTarget).trim();
|
|
300
|
+
if (instruction.startsWith(INTERCOM_BRIDGE_MARKER)) return instruction;
|
|
301
|
+
return `${INTERCOM_BRIDGE_MARKER}
|
|
302
|
+
${instruction}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function diagnoseIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeDiagnostic {
|
|
306
|
+
const config = resolveIntercomBridgeConfig(input.config);
|
|
307
|
+
const mode = config.mode;
|
|
308
|
+
const agentDir = path.resolve(input.agentDir ?? defaultAgentDir());
|
|
309
|
+
const extensionDir = resolveIntercomExtensionDir(input, agentDir);
|
|
310
|
+
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
311
|
+
const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath(agentDir));
|
|
312
|
+
const wantsIntercom = mode !== "off" && !(mode === "fork-only" && input.context !== "fork");
|
|
313
|
+
const piIntercomAvailable = fs.existsSync(extensionDir);
|
|
314
|
+
let configStatus: ReturnType<typeof intercomConfigStatus> | undefined;
|
|
315
|
+
let reason: string | undefined;
|
|
316
|
+
if (mode === "off") reason = "bridge mode is off";
|
|
317
|
+
else if (mode === "fork-only" && input.context !== "fork") reason = "bridge mode is fork-only and context is not fork";
|
|
318
|
+
else if (!orchestratorTarget) reason = "orchestrator target is not available";
|
|
319
|
+
else if (!piIntercomAvailable) reason = "pi-intercom extension was not found";
|
|
320
|
+
else {
|
|
321
|
+
configStatus = intercomConfigStatus(configPath);
|
|
322
|
+
if (!configStatus.enabled) reason = "intercom config is disabled";
|
|
323
|
+
}
|
|
324
|
+
let intercomConfigError: string | undefined;
|
|
325
|
+
if (configStatus?.error) {
|
|
326
|
+
const error = configStatus.error;
|
|
327
|
+
intercomConfigError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
active: reason === undefined,
|
|
332
|
+
mode,
|
|
333
|
+
wantsIntercom,
|
|
334
|
+
piIntercomAvailable,
|
|
335
|
+
extensionDir,
|
|
336
|
+
configPath,
|
|
337
|
+
...(orchestratorTarget ? { orchestratorTarget } : {}),
|
|
338
|
+
...(reason ? { reason } : {}),
|
|
339
|
+
...(configStatus ? { intercomConfigEnabled: configStatus.enabled } : {}),
|
|
340
|
+
...(intercomConfigError ? { intercomConfigError } : {}),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeState {
|
|
345
|
+
const config = resolveIntercomBridgeConfig(input.config);
|
|
346
|
+
const mode = config.mode;
|
|
347
|
+
const agentDir = path.resolve(input.agentDir ?? defaultAgentDir());
|
|
348
|
+
const extensionDir = resolveIntercomExtensionDir(input, agentDir);
|
|
349
|
+
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
350
|
+
const settingsDir = path.resolve(input.settingsDir ?? defaultSubagentConfigDir(agentDir));
|
|
351
|
+
const defaultInstruction = buildIntercomBridgeInstruction(
|
|
352
|
+
orchestratorTarget || "{orchestratorTarget}",
|
|
353
|
+
DEFAULT_INTERCOM_BRIDGE_TEMPLATE,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (mode === "off") {
|
|
357
|
+
return { active: false, mode, extensionDir, instruction: defaultInstruction };
|
|
358
|
+
}
|
|
359
|
+
if (mode === "fork-only" && input.context !== "fork") {
|
|
360
|
+
return { active: false, mode, extensionDir, instruction: defaultInstruction };
|
|
361
|
+
}
|
|
362
|
+
if (!orchestratorTarget) {
|
|
363
|
+
return { active: false, mode, extensionDir, instruction: defaultInstruction };
|
|
364
|
+
}
|
|
365
|
+
if (!fs.existsSync(extensionDir)) {
|
|
366
|
+
return { active: false, mode, extensionDir, instruction: defaultInstruction };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath(agentDir));
|
|
370
|
+
const intercomStatus = intercomConfigStatus(configPath);
|
|
371
|
+
if (intercomStatus.error) console.warn(`Failed to parse intercom config at '${configPath}'. Assuming enabled.`, intercomStatus.error);
|
|
372
|
+
if (!intercomStatus.enabled) {
|
|
373
|
+
return { active: false, mode, extensionDir, instruction: defaultInstruction };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const instruction = buildIntercomBridgeInstruction(
|
|
377
|
+
orchestratorTarget,
|
|
378
|
+
resolveInstructionTemplate(config.instructionFile, settingsDir),
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
active: true,
|
|
383
|
+
mode,
|
|
384
|
+
orchestratorTarget,
|
|
385
|
+
extensionDir,
|
|
386
|
+
instruction,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomBridgeState): AgentConfig {
|
|
391
|
+
if (!bridge.active || !bridge.orchestratorTarget) return agent;
|
|
392
|
+
if (!extensionSandboxAllowsIntercom(agent.extensions, bridge.extensionDir)) return agent;
|
|
393
|
+
|
|
394
|
+
const bridgeTools = ["intercom", "contact_supervisor"];
|
|
395
|
+
const tools = agent.tools
|
|
396
|
+
? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))]
|
|
397
|
+
: agent.tools;
|
|
398
|
+
const instruction = bridge.instruction;
|
|
399
|
+
const trimmedPrompt = agent.systemPrompt?.trim() || "";
|
|
400
|
+
const systemPrompt = trimmedPrompt.includes(INTERCOM_BRIDGE_MARKER)
|
|
401
|
+
? trimmedPrompt
|
|
402
|
+
: trimmedPrompt
|
|
403
|
+
? `${trimmedPrompt}\n\n${instruction}`
|
|
404
|
+
: instruction;
|
|
405
|
+
|
|
406
|
+
if (tools === agent.tools && systemPrompt === agent.systemPrompt) return agent;
|
|
407
|
+
return {
|
|
408
|
+
...agent,
|
|
409
|
+
tools,
|
|
410
|
+
systemPrompt,
|
|
411
|
+
};
|
|
412
|
+
}
|