pi-subagents 0.80.0 → 0.82.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/CHANGELOG.md +66 -0
- package/bundle/agents.js +18 -0
- package/bundle/background-work.js +16 -0
- package/bundle/capability-ceiling.js +22 -0
- package/bundle/child-tool-plan.js +11 -0
- package/bundle/chunk-43SCHIMD.js +57 -0
- package/bundle/chunk-4QFZZV3O.js +57 -0
- package/bundle/chunk-4SDU2BZX.js +1190 -0
- package/bundle/chunk-52UBALJL.js +753 -0
- package/bundle/chunk-5LFLVSTM.js +260 -0
- package/bundle/chunk-5TYMSZX3.js +235 -0
- package/bundle/chunk-62OCY2S4.js +14 -0
- package/bundle/chunk-7F6RKT6W.js +132 -0
- package/bundle/chunk-7IVSUMQQ.js +1337 -0
- package/bundle/chunk-7J4ZXMY7.js +18 -0
- package/bundle/chunk-7PCKZHR5.js +1234 -0
- package/bundle/chunk-7YMXOYEU.js +1373 -0
- package/bundle/chunk-B5EKXDHE.js +16870 -0
- package/bundle/chunk-CIZQXJ5Q.js +8619 -0
- package/bundle/chunk-CTPAE36Y.js +94 -0
- package/bundle/chunk-D4EHT5SJ.js +14 -0
- package/bundle/chunk-GC4DSPMH.js +946 -0
- package/bundle/chunk-GJECHSAR.js +179 -0
- package/bundle/chunk-M2CTFYT3.js +4164 -0
- package/bundle/chunk-MHZ4G23A.js +1296 -0
- package/bundle/chunk-N2BN7BF5.js +159 -0
- package/bundle/chunk-NF7IEKBH.js +490 -0
- package/bundle/chunk-NGGAFDL6.js +742 -0
- package/bundle/chunk-NIXWDZZV.js +9 -0
- package/bundle/chunk-NRQ75MGH.js +8327 -0
- package/bundle/chunk-OYF6AFDY.js +458 -0
- package/bundle/chunk-PLCCBLPY.js +526 -0
- package/bundle/chunk-RMQFMMAT.js +416 -0
- package/bundle/chunk-SIJ4S3EN.js +276 -0
- package/bundle/chunk-UKG4V45O.js +228 -0
- package/bundle/chunk-VH5OAWVG.js +4096 -0
- package/bundle/chunk-W2SZ22EM.js +5462 -0
- package/bundle/chunk-WVD4UEL4.js +9367 -0
- package/bundle/chunk-XSWOTIA3.js +175 -0
- package/bundle/chunk-YFCK7H2S.js +129 -0
- package/bundle/chunk-YKEWIB4M.js +380 -0
- package/bundle/chunk-YYREO77V.js +137 -0
- package/bundle/chunk-Z7KVHQHL.js +173 -0
- package/bundle/chunk-ZLINIYTP.js +8613 -0
- package/bundle/chunk-ZQGSDHL6.js +128 -0
- package/bundle/control-channel.js +11 -0
- package/bundle/delegation.js +14 -0
- package/bundle/external-job-provider.js +20 -0
- package/bundle/external-runs.js +21 -0
- package/bundle/index.js +30 -0
- package/bundle/inspectors.js +8 -0
- package/bundle/intercom-bridge.js +11 -0
- package/bundle/preflight.js +425 -0
- package/bundle/project-panes.js +28 -0
- package/bundle/required-child-extensions.js +6 -0
- package/bundle/shared-types.js +8 -0
- package/bundle/workflow-resources.js +7 -0
- package/docs/agents.md +31 -0
- package/docs/configuration.md +9 -1
- package/docs/extension-api.md +27 -0
- package/docs/observability.md +6 -2
- package/docs/tool-reference.md +3 -1
- package/docs/workflows.md +16 -0
- package/package.json +19 -19
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/src/agents/agent-management.js +3 -3
- package/src/agents/agent-memory.js +3 -1
- package/src/agents/agents.js +6 -6
- package/src/agents/frontmatter.d.ts +4 -0
- package/src/agents/frontmatter.js +24 -3
- package/src/agents/skills.js +2 -1
- package/src/api/delegation.d.ts +20 -0
- package/src/extension/config.js +3 -0
- package/src/extension/herdr-pi-bridge.js +17 -10
- package/src/extension/index.js +109 -9
- package/src/extension/schemas.js +3 -3
- package/src/intercom/native-supervisor-channel.js +20 -0
- package/src/missions/lifecycle.js +7 -1
- package/src/runs/background/active-async-capacity.d.ts +1 -0
- package/src/runs/background/active-async-capacity.js +9 -3
- package/src/runs/background/async-execution.js +20 -10
- package/src/runs/background/async-job-tracker.d.ts +6 -1
- package/src/runs/background/async-job-tracker.js +9 -4
- package/src/runs/background/async-status.d.ts +2 -0
- package/src/runs/background/async-status.js +21 -2
- package/src/runs/background/await-async-run.js +3 -7
- package/src/runs/background/boot-id.d.ts +12 -0
- package/src/runs/background/boot-id.js +47 -0
- package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
- package/src/runs/background/interrupted-elsewhere.js +68 -0
- package/src/runs/background/notify.d.ts +2 -0
- package/src/runs/background/notify.js +13 -0
- package/src/runs/background/result-delivery-ownership.d.ts +6 -0
- package/src/runs/background/result-delivery-ownership.js +19 -1
- package/src/runs/background/result-watcher.d.ts +3 -1
- package/src/runs/background/result-watcher.js +24 -8
- package/src/runs/background/run-child-session.d.ts +1 -6
- package/src/runs/background/run-child-session.js +19 -1
- package/src/runs/background/run-status.js +2 -1
- package/src/runs/background/stale-run-reconciler.d.ts +25 -0
- package/src/runs/background/stale-run-reconciler.js +54 -13
- package/src/runs/background/subagent-runner-bootstrap.js +3 -0
- package/src/runs/background/subagent-runner.d.ts +4 -0
- package/src/runs/background/subagent-runner.js +13 -5
- package/src/runs/background/subagent-wait.d.ts +1 -1
- package/src/runs/background/subagent-wait.js +6 -1
- package/src/runs/background/terminal-run-index.d.ts +1 -0
- package/src/runs/background/terminal-run-index.js +4 -2
- package/src/runs/foreground/async-stop-action.js +3 -1
- package/src/runs/foreground/execution.js +45 -5
- package/src/runs/foreground/foreground-history.d.ts +16 -2
- package/src/runs/foreground/foreground-history.js +133 -31
- package/src/runs/foreground/subagent-executor.d.ts +11 -0
- package/src/runs/foreground/subagent-executor.js +277 -74
- package/src/runs/shared/child-launch.d.ts +2 -2
- package/src/runs/shared/child-session.d.ts +3 -2
- package/src/runs/shared/child-session.js +51 -8
- package/src/runs/shared/child-tool-plan.d.ts +7 -0
- package/src/runs/shared/child-tool-plan.js +21 -6
- package/src/runs/shared/herdr-connection.d.ts +17 -8
- package/src/runs/shared/herdr-connection.js +19 -7
- package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
- package/src/runs/shared/herdr-external-adapters.js +1 -1
- package/src/runs/shared/herdr-machine.d.ts +12 -2
- package/src/runs/shared/herdr-machine.js +55 -9
- package/src/runs/shared/herdr-pane-host.d.ts +88 -0
- package/src/runs/shared/herdr-pane-host.js +155 -0
- package/src/runs/shared/herdr-placed-run.d.ts +27 -150
- package/src/runs/shared/herdr-placed-run.js +48 -395
- package/src/runs/shared/local-machine-access.d.ts +5 -0
- package/src/runs/shared/local-machine-access.js +28 -0
- package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
- package/src/runs/shared/orca-progress-tabs.js +1 -24
- package/src/runs/shared/parallel-utils.d.ts +1 -1
- package/src/runs/shared/placed-run-owner.d.ts +49 -0
- package/src/runs/shared/placed-run-owner.js +93 -0
- package/src/runs/shared/placement-host.d.ts +99 -0
- package/src/runs/shared/placement-host.js +157 -0
- package/src/runs/shared/rex-machine.d.ts +25 -0
- package/src/runs/shared/rex-machine.js +83 -0
- package/src/runs/shared/rex-pane-host.d.ts +62 -0
- package/src/runs/shared/rex-pane-host.js +327 -0
- package/src/runs/shared/structured-output.d.ts +1 -0
- package/src/runs/shared/structured-output.js +1 -0
- package/src/shared/accessible-dir.js +4 -1
- package/src/shared/completion-owner.d.ts +5 -0
- package/src/shared/completion-owner.js +5 -0
- package/src/shared/provider-diagnostics.d.ts +9 -0
- package/src/shared/provider-diagnostics.js +75 -0
- package/src/shared/session-identity.d.ts +2 -0
- package/src/shared/session-identity.js +26 -0
- package/src/shared/types.d.ts +48 -6
- package/src/slash/delegation-adapters.d.ts +3 -1
- package/src/slash/delegation-adapters.js +7 -0
- package/src/slash/prompt-template-bridge.js +3 -1
- package/src/slash/subagents-admin.d.ts +1 -0
- package/src/slash/subagents-admin.js +1 -1
- package/src/tui/fleet-layout.d.ts +20 -0
- package/src/tui/fleet-layout.js +17 -0
- package/src/tui/fleet-viewport.d.ts +12 -0
- package/src/tui/fleet-viewport.js +35 -0
- package/src/tui/fleet.d.ts +6 -6
- package/src/tui/fleet.js +93 -66
- package/src/tui/render.d.ts +6 -0
- package/src/tui/render.js +3 -3
- package/src/tui/subagent-messages.js +1 -0
- package/src/workflows/scripted-workflow.d.ts +1 -1
- package/src/workflows/structured-workflow-scripts.d.ts +1 -0
- package/src/workflows/structured-workflow-scripts.js +5 -4
- package/src/workflows/workflow-auto-resume.d.ts +32 -0
- package/src/workflows/workflow-auto-resume.js +102 -0
- package/src/workflows/workflow-launch-record.d.ts +27 -0
- package/src/workflows/workflow-launch-record.js +48 -0
- package/src/workflows/workflow-resources.js +2 -2
- package/src/workflows/workflow-resume-claim.d.ts +82 -0
- package/src/workflows/workflow-resume-claim.js +210 -0
- package/src/workflows/workflow-resume.d.ts +56 -0
- package/src/workflows/workflow-resume.js +106 -0
- package/src/workflows/workflow-reuse.d.ts +15 -9
- package/src/workflows/workflow-reuse.js +77 -14
- package/src/workflows/workflow-revival.d.ts +2 -0
- package/src/workflows/workflow-revival.js +2 -1
- package/src/workflows/workflow-stop-cause.d.ts +6 -0
- package/src/workflows/workflow-stop-cause.js +8 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import {
|
|
2
|
+
agentDefinitionDigest
|
|
3
|
+
} from "./chunk-7F6RKT6W.js";
|
|
4
|
+
import {
|
|
5
|
+
getAgentDir
|
|
6
|
+
} from "./chunk-CIZQXJ5Q.js";
|
|
7
|
+
|
|
8
|
+
// src/intercom/intercom-bridge.ts
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
var NATIVE_INTERCOM_EXTENSION_DIR = "native:pi-subagents-supervisor-channel";
|
|
13
|
+
function defaultAgentDir() {
|
|
14
|
+
return getAgentDir();
|
|
15
|
+
}
|
|
16
|
+
function defaultSubagentConfigDir(agentDir = defaultAgentDir()) {
|
|
17
|
+
return path.join(agentDir, "extensions", "subagent");
|
|
18
|
+
}
|
|
19
|
+
var DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat";
|
|
20
|
+
var PI_INTERCOM_SESSION_ID_ENV = "PI_INTERCOM_SESSION_ID";
|
|
21
|
+
var INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
22
|
+
var ORCHESTRATOR_TARGET_PLACEHOLDER = "{orchestratorTarget}";
|
|
23
|
+
var 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.
|
|
24
|
+
|
|
25
|
+
Use contact_supervisor first. It resolves the supervisor session and run metadata automatically.
|
|
26
|
+
- Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "<question>" })
|
|
27
|
+
- Need structured supervisor input rather than a freeform reply: contact_supervisor({ reason: "interview_request", message: "<what input is needed>", interview: { title: "...", questions: [] } })
|
|
28
|
+
- After contact_supervisor with reason "need_decision" or "interview_request", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
|
|
29
|
+
- Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. If an output path is configured but no write-capable tool is available, return the complete artifact in your final response; the runtime will persist it. Do not contact the supervisor merely because you cannot write that output path directly.
|
|
30
|
+
- Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
|
|
31
|
+
|
|
32
|
+
Do not use contact_supervisor for routine completion handoffs. If no coordination is needed, return a focused task result.`;
|
|
33
|
+
function validateIntercomBridgeConfig({ value, label }) {
|
|
34
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { ok: false, error: `${label} must be an object.` };
|
|
35
|
+
const bridge = value;
|
|
36
|
+
for (const field of Object.keys(bridge)) {
|
|
37
|
+
if (field !== "mode" && field !== "instructionFile" && field !== "resultDelivery") return { ok: false, error: `${label}.${field} is not supported.` };
|
|
38
|
+
}
|
|
39
|
+
if (bridge.mode !== void 0 && bridge.mode !== "off" && bridge.mode !== "fork-only" && bridge.mode !== "always") return { ok: false, error: `${label}.mode is invalid.` };
|
|
40
|
+
if (bridge.instructionFile !== void 0 && typeof bridge.instructionFile !== "string") return { ok: false, error: `${label}.instructionFile must be a string.` };
|
|
41
|
+
if (bridge.resultDelivery !== void 0 && typeof bridge.resultDelivery !== "boolean") return { ok: false, error: `${label}.resultDelivery must be a boolean.` };
|
|
42
|
+
return {
|
|
43
|
+
ok: true,
|
|
44
|
+
value: {
|
|
45
|
+
...bridge.mode !== void 0 ? { mode: bridge.mode } : {},
|
|
46
|
+
...bridge.instructionFile !== void 0 ? { instructionFile: bridge.instructionFile } : {},
|
|
47
|
+
...bridge.resultDelivery !== void 0 ? { resultDelivery: bridge.resultDelivery } : {}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function resolveIntercomSessionTarget(sessionName, sessionId, intercomSessionId = process.env[PI_INTERCOM_SESSION_ID_ENV]) {
|
|
52
|
+
const trimmedName = sessionName?.trim();
|
|
53
|
+
if (trimmedName) return trimmedName;
|
|
54
|
+
const fallbackSessionId = intercomSessionId?.trim() || sessionId;
|
|
55
|
+
const normalizedSessionId = fallbackSessionId.startsWith("session-") ? fallbackSessionId.slice("session-".length) : fallbackSessionId;
|
|
56
|
+
return `${DEFAULT_INTERCOM_TARGET_PREFIX}-${normalizedSessionId.slice(0, 18)}`;
|
|
57
|
+
}
|
|
58
|
+
function sanitizeIntercomTargetPart(value) {
|
|
59
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
60
|
+
}
|
|
61
|
+
function resolveSubagentIntercomTarget(runId, agent, index) {
|
|
62
|
+
const stepSuffix = index !== void 0 ? `-${index + 1}` : "";
|
|
63
|
+
return `subagent-${sanitizeIntercomTargetPart(agent)}-${sanitizeIntercomTargetPart(runId)}${stepSuffix}`;
|
|
64
|
+
}
|
|
65
|
+
function resolveIntercomBridgeMode(value) {
|
|
66
|
+
if (value === "off" || value === "always" || value === "fork-only") return value;
|
|
67
|
+
return "always";
|
|
68
|
+
}
|
|
69
|
+
function resolveIntercomBridgeConfig(value) {
|
|
70
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
71
|
+
return { mode: "always", instructionFile: "", resultDelivery: false };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
mode: resolveIntercomBridgeMode(value.mode),
|
|
75
|
+
instructionFile: typeof value.instructionFile === "string" ? value.instructionFile : "",
|
|
76
|
+
resultDelivery: value.resultDelivery === true
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function expandTilde(filePath) {
|
|
80
|
+
return filePath.startsWith("~/") ? path.join(os.homedir(), filePath.slice(2)) : filePath;
|
|
81
|
+
}
|
|
82
|
+
function resolveInstructionTemplate(instructionFile, settingsDir) {
|
|
83
|
+
if (!instructionFile) return DEFAULT_INTERCOM_BRIDGE_TEMPLATE;
|
|
84
|
+
const expandedPath = expandTilde(instructionFile);
|
|
85
|
+
const resolvedPath = path.isAbsolute(expandedPath) ? expandedPath : path.resolve(settingsDir, expandedPath);
|
|
86
|
+
try {
|
|
87
|
+
return fs.readFileSync(resolvedPath, "utf-8");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn(`Failed to read intercom bridge instructionFile at '${resolvedPath}'. Using default instructions.`, error);
|
|
90
|
+
return DEFAULT_INTERCOM_BRIDGE_TEMPLATE;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function buildIntercomBridgeInstruction(orchestratorTarget, template) {
|
|
94
|
+
const instruction = template.replaceAll(ORCHESTRATOR_TARGET_PLACEHOLDER, orchestratorTarget).trim();
|
|
95
|
+
if (instruction.startsWith(INTERCOM_BRIDGE_MARKER)) return instruction;
|
|
96
|
+
return `${INTERCOM_BRIDGE_MARKER}
|
|
97
|
+
${instruction}`;
|
|
98
|
+
}
|
|
99
|
+
function inactiveReason(mode, context, orchestratorTarget) {
|
|
100
|
+
if (mode === "off") return "bridge mode is off";
|
|
101
|
+
if (mode === "fork-only" && context !== "fork") return "bridge mode is fork-only and context is not fork";
|
|
102
|
+
if (!orchestratorTarget) return "orchestrator target is not available";
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
function diagnoseIntercomBridge(input) {
|
|
106
|
+
const config = resolveIntercomBridgeConfig(input.config);
|
|
107
|
+
const mode = config.mode;
|
|
108
|
+
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
109
|
+
const wantsIntercom = mode !== "off" && !(mode === "fork-only" && input.context !== "fork");
|
|
110
|
+
const reason = inactiveReason(mode, input.context, orchestratorTarget);
|
|
111
|
+
return {
|
|
112
|
+
active: reason === void 0,
|
|
113
|
+
mode,
|
|
114
|
+
wantsIntercom,
|
|
115
|
+
supervisorChannelAvailable: true,
|
|
116
|
+
extensionDir: NATIVE_INTERCOM_EXTENSION_DIR,
|
|
117
|
+
...orchestratorTarget ? { orchestratorTarget } : {},
|
|
118
|
+
...reason ? { reason } : {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function resolveIntercomBridge(input) {
|
|
122
|
+
const config = resolveIntercomBridgeConfig(input.override !== void 0 ? input.override : input.config);
|
|
123
|
+
const mode = config.mode;
|
|
124
|
+
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
125
|
+
const agentDir = path.resolve(input.agentDir ?? defaultAgentDir());
|
|
126
|
+
const settingsDir = path.resolve(input.settingsDir ?? defaultSubagentConfigDir(agentDir));
|
|
127
|
+
const reason = inactiveReason(mode, input.context, orchestratorTarget);
|
|
128
|
+
if (reason || !orchestratorTarget) {
|
|
129
|
+
return {
|
|
130
|
+
active: false,
|
|
131
|
+
mode,
|
|
132
|
+
resultDelivery: config.resultDelivery,
|
|
133
|
+
extensionDir: NATIVE_INTERCOM_EXTENSION_DIR,
|
|
134
|
+
instruction: buildIntercomBridgeInstruction(ORCHESTRATOR_TARGET_PLACEHOLDER, DEFAULT_INTERCOM_BRIDGE_TEMPLATE),
|
|
135
|
+
interpolatesOrchestratorTarget: false
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const template = resolveInstructionTemplate(config.instructionFile, settingsDir);
|
|
139
|
+
return {
|
|
140
|
+
active: true,
|
|
141
|
+
mode,
|
|
142
|
+
resultDelivery: config.resultDelivery,
|
|
143
|
+
orchestratorTarget,
|
|
144
|
+
extensionDir: NATIVE_INTERCOM_EXTENSION_DIR,
|
|
145
|
+
instruction: buildIntercomBridgeInstruction(orchestratorTarget, template),
|
|
146
|
+
interpolatesOrchestratorTarget: template.includes(ORCHESTRATOR_TARGET_PLACEHOLDER)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function applyIntercomBridgeToAgent(agent, bridge) {
|
|
150
|
+
if (!bridge.active || !bridge.orchestratorTarget) return agent;
|
|
151
|
+
const bridgeTools = ["contact_supervisor"];
|
|
152
|
+
const tools = agent.tools && agent.tools.length > 0 ? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))] : agent.tools;
|
|
153
|
+
const instruction = bridge.instruction;
|
|
154
|
+
const trimmedPrompt = agent.systemPrompt?.trim() || "";
|
|
155
|
+
const systemPrompt = trimmedPrompt.includes(INTERCOM_BRIDGE_MARKER) ? trimmedPrompt : trimmedPrompt ? `${trimmedPrompt}
|
|
156
|
+
|
|
157
|
+
${instruction}` : instruction;
|
|
158
|
+
if (tools === agent.tools && systemPrompt === agent.systemPrompt) return agent;
|
|
159
|
+
return {
|
|
160
|
+
...agent,
|
|
161
|
+
definitionDigest: agentDefinitionDigest(agent),
|
|
162
|
+
tools,
|
|
163
|
+
systemPrompt
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
INTERCOM_BRIDGE_MARKER,
|
|
169
|
+
validateIntercomBridgeConfig,
|
|
170
|
+
resolveIntercomSessionTarget,
|
|
171
|
+
resolveSubagentIntercomTarget,
|
|
172
|
+
diagnoseIntercomBridge,
|
|
173
|
+
resolveIntercomBridge,
|
|
174
|
+
applyIntercomBridgeToAgent
|
|
175
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/shared/display-text.ts
|
|
2
|
+
function codePointWidth(codePoint) {
|
|
3
|
+
return codePoint > 65535 ? 2 : 1;
|
|
4
|
+
}
|
|
5
|
+
function isWhitespaceOrControl(codePoint) {
|
|
6
|
+
if (codePoint <= 32 || codePoint >= 127 && codePoint <= 159) return true;
|
|
7
|
+
if (codePoint >= 55296 && codePoint <= 57343) return true;
|
|
8
|
+
return String.fromCodePoint(codePoint).trim().length === 0;
|
|
9
|
+
}
|
|
10
|
+
function consumeControlString(value, index, osc) {
|
|
11
|
+
while (index < value.length) {
|
|
12
|
+
const codePoint = value.codePointAt(index);
|
|
13
|
+
const width = codePointWidth(codePoint);
|
|
14
|
+
if (osc && codePoint === 7) return index + width;
|
|
15
|
+
if (codePoint === 156) return index + width;
|
|
16
|
+
if (codePoint === 27 && value.charCodeAt(index + 1) === 92) return index + 2;
|
|
17
|
+
index += width;
|
|
18
|
+
}
|
|
19
|
+
return value.length;
|
|
20
|
+
}
|
|
21
|
+
function consumeCsi(value, index) {
|
|
22
|
+
while (index < value.length) {
|
|
23
|
+
const codePoint = value.codePointAt(index);
|
|
24
|
+
const width = codePointWidth(codePoint);
|
|
25
|
+
if (codePoint >= 64 && codePoint <= 126) return index + width;
|
|
26
|
+
index += width;
|
|
27
|
+
}
|
|
28
|
+
return value.length;
|
|
29
|
+
}
|
|
30
|
+
function sanitizeDisplayText(value) {
|
|
31
|
+
const output = [];
|
|
32
|
+
let pendingSpace = false;
|
|
33
|
+
const appendSpace = () => {
|
|
34
|
+
if (output.length > 0) pendingSpace = true;
|
|
35
|
+
};
|
|
36
|
+
const appendText = (text) => {
|
|
37
|
+
if (pendingSpace) output.push(" ");
|
|
38
|
+
output.push(text);
|
|
39
|
+
pendingSpace = false;
|
|
40
|
+
};
|
|
41
|
+
for (let index = 0; index < value.length; ) {
|
|
42
|
+
const codePoint = value.codePointAt(index);
|
|
43
|
+
const width = codePointWidth(codePoint);
|
|
44
|
+
if (codePoint === 27) {
|
|
45
|
+
const next = value.charCodeAt(index + 1);
|
|
46
|
+
appendSpace();
|
|
47
|
+
if (next === 91) {
|
|
48
|
+
index = consumeCsi(value, index + 2);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (next === 93 || next === 80 || next === 88 || next === 94 || next === 95) {
|
|
52
|
+
index = consumeControlString(value, index + 2, next === 93);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
index += next ? 2 : 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (codePoint === 155) {
|
|
59
|
+
appendSpace();
|
|
60
|
+
index = consumeCsi(value, index + width);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (codePoint === 144 || codePoint === 152 || codePoint === 157 || codePoint === 158 || codePoint === 159) {
|
|
64
|
+
appendSpace();
|
|
65
|
+
index = consumeControlString(value, index + width, codePoint === 157);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (isWhitespaceOrControl(codePoint)) appendSpace();
|
|
69
|
+
else appendText(String.fromCodePoint(codePoint));
|
|
70
|
+
index += width;
|
|
71
|
+
}
|
|
72
|
+
return output.join("");
|
|
73
|
+
}
|
|
74
|
+
function truncateDisplayText(value, maxLength) {
|
|
75
|
+
if (maxLength <= 0) return "";
|
|
76
|
+
if (value.length <= maxLength) return value;
|
|
77
|
+
let output = "";
|
|
78
|
+
for (const char of value) {
|
|
79
|
+
if (output.length + char.length > maxLength) break;
|
|
80
|
+
output += char;
|
|
81
|
+
}
|
|
82
|
+
return output;
|
|
83
|
+
}
|
|
84
|
+
function previewDisplayText(value, maxLength) {
|
|
85
|
+
const normalized = sanitizeDisplayText(value);
|
|
86
|
+
if (normalized.length <= maxLength) return normalized;
|
|
87
|
+
if (maxLength <= 3) return truncateDisplayText(normalized, maxLength);
|
|
88
|
+
return `${truncateDisplayText(normalized, maxLength - 3)}...`;
|
|
89
|
+
}
|
|
90
|
+
var BINARY_CONTENT_PLACEHOLDER = "[binary content omitted for safe display]";
|
|
91
|
+
function isUnsafeTerminalCodePoint(codePoint) {
|
|
92
|
+
const terminalControl = codePoint <= 31 && codePoint !== 9 && codePoint !== 10 || codePoint >= 127 && codePoint <= 159;
|
|
93
|
+
const bidiControl = codePoint === 1564 || codePoint === 8206 || codePoint === 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
|
|
94
|
+
const privateUse = codePoint >= 57344 && codePoint <= 63743 || codePoint >= 983040 && codePoint <= 1048573 || codePoint >= 1048576 && codePoint <= 1114109;
|
|
95
|
+
const invalidScalar = codePoint >= 55296 && codePoint <= 57343;
|
|
96
|
+
const nonCharacter = codePoint >= 64976 && codePoint <= 65007 || (codePoint & 65535) === 65534 || (codePoint & 65535) === 65535;
|
|
97
|
+
return terminalControl || bidiControl || privateUse || invalidScalar || nonCharacter;
|
|
98
|
+
}
|
|
99
|
+
function looksLikeBinaryContent(text) {
|
|
100
|
+
if (text.includes("\0")) return true;
|
|
101
|
+
let suspiciousControls = 0;
|
|
102
|
+
let replacementCharacters = 0;
|
|
103
|
+
let codePoints = 0;
|
|
104
|
+
for (const character of text) {
|
|
105
|
+
codePoints++;
|
|
106
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
107
|
+
if (codePoint <= 8 || codePoint >= 14 && codePoint <= 31) suspiciousControls++;
|
|
108
|
+
if (codePoint === 65533) replacementCharacters++;
|
|
109
|
+
}
|
|
110
|
+
if (codePoints === 0) return false;
|
|
111
|
+
return suspiciousControls >= 4 && suspiciousControls / codePoints >= 0.1 || replacementCharacters >= 3 && replacementCharacters / codePoints >= 0.1;
|
|
112
|
+
}
|
|
113
|
+
function safeTerminalText(value) {
|
|
114
|
+
const normalized = value.replace(/\r\n/g, "\n");
|
|
115
|
+
if (looksLikeBinaryContent(normalized)) return BINARY_CONTENT_PLACEHOLDER;
|
|
116
|
+
let safe = "";
|
|
117
|
+
for (const character of normalized) {
|
|
118
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
119
|
+
safe += isUnsafeTerminalCodePoint(codePoint) ? `[U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}]` : character;
|
|
120
|
+
}
|
|
121
|
+
return safe;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
sanitizeDisplayText,
|
|
126
|
+
truncateDisplayText,
|
|
127
|
+
previewDisplayText,
|
|
128
|
+
safeTerminalText
|
|
129
|
+
};
|