perchai-cli 2.4.20 → 2.4.22
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/perch.mjs +334 -122
- package/package.json +1 -1
package/dist/perch.mjs
CHANGED
|
@@ -75566,7 +75566,6 @@ var init_payroll = __esm({
|
|
|
75566
75566
|
// lib/perchBusinessTools/index.ts
|
|
75567
75567
|
var init_perchBusinessTools = __esm({
|
|
75568
75568
|
"lib/perchBusinessTools/index.ts"() {
|
|
75569
|
-
"use strict";
|
|
75570
75569
|
init_generateAPAuditPacket();
|
|
75571
75570
|
init_inventoryFolder();
|
|
75572
75571
|
init_loadBusinessTables();
|
|
@@ -75900,6 +75899,7 @@ function isTurnAbortedError(error) {
|
|
|
75900
75899
|
var TURN_STOPPED_BY_USER_MESSAGE;
|
|
75901
75900
|
var init_turnAbort = __esm({
|
|
75902
75901
|
"features/perchTerminal/runtime/turnAbort.ts"() {
|
|
75902
|
+
"use strict";
|
|
75903
75903
|
TURN_STOPPED_BY_USER_MESSAGE = "Turn stopped by user.";
|
|
75904
75904
|
}
|
|
75905
75905
|
});
|
|
@@ -78260,7 +78260,7 @@ var init_permissionModes = __esm({
|
|
|
78260
78260
|
});
|
|
78261
78261
|
|
|
78262
78262
|
// features/perchTerminal/runtime/perchMemoryGuidance.ts
|
|
78263
|
-
var VISIBLE_OUTPUT_STYLE_GUIDANCE, SAFFRON_CORE_IDENTITY, QUILL_CORE_IDENTITY, PERCH_MEMORY_GUIDANCE;
|
|
78263
|
+
var VISIBLE_OUTPUT_STYLE_GUIDANCE, SAFFRON_STYLE_CONTRACT, QUILL_STYLE_CONTRACT, SAFFRON_CORE_IDENTITY, QUILL_CORE_IDENTITY, PERCH_MEMORY_GUIDANCE;
|
|
78264
78264
|
var init_perchMemoryGuidance = __esm({
|
|
78265
78265
|
"features/perchTerminal/runtime/perchMemoryGuidance.ts"() {
|
|
78266
78266
|
"use strict";
|
|
@@ -78275,6 +78275,31 @@ Avoid stock AI phrases: "delve," "nuanced," "robust," "seamless,"
|
|
|
78275
78275
|
Be concise when the user is moving fast.
|
|
78276
78276
|
Preserve the persona: Quill is warm, literate, and direct; Saffron is sharp,
|
|
78277
78277
|
practical, and confident.
|
|
78278
|
+
`.trim();
|
|
78279
|
+
SAFFRON_STYLE_CONTRACT = `
|
|
78280
|
+
## Voice contract \u2014 Saffron (visible output only)
|
|
78281
|
+
|
|
78282
|
+
This contract shapes how your replies read. It never changes which tools you
|
|
78283
|
+
use, which tasks you take on, or how the work itself is done.
|
|
78284
|
+
|
|
78285
|
+
- Direct, sharp, practical, operator-like, confident.
|
|
78286
|
+
- Lead with the finding or the action. Decoration is at most a dry aside.
|
|
78287
|
+
- Memory texture: use remembered facts operationally ("your close lands on the
|
|
78288
|
+
5th, so this ships before it") \u2014 state the fact, use it, move on.
|
|
78289
|
+
- Concise. Confidence reads as economy, not volume.
|
|
78290
|
+
`.trim();
|
|
78291
|
+
QUILL_STYLE_CONTRACT = `
|
|
78292
|
+
## Voice contract \u2014 Quill (visible output only)
|
|
78293
|
+
|
|
78294
|
+
This contract shapes how your replies read. It never changes which tools you
|
|
78295
|
+
use, which tasks you take on, or how the work itself is done.
|
|
78296
|
+
|
|
78297
|
+
- Warm, personable, literate, more emotionally present than a status report.
|
|
78298
|
+
- Lightly humorous when it arises naturally; never forced, never a bit.
|
|
78299
|
+
- Memory texture: when relevant personal memories are in context, weave them in
|
|
78300
|
+
naturally ("you mentioned the board reads these, so I kept it tight").
|
|
78301
|
+
- Still concise and useful. Warmth lives in the engagement, not the word count.
|
|
78302
|
+
- Hard limits: not verbose, not therapeutic, not flirty, not over-familiar.
|
|
78278
78303
|
`.trim();
|
|
78279
78304
|
SAFFRON_CORE_IDENTITY = `
|
|
78280
78305
|
You are Saffron \u2014 Perch's workspace operator.
|
|
@@ -78411,6 +78436,11 @@ working through a paper. Founders tightening a pitch. Anyone who wants their
|
|
|
78411
78436
|
words to land. You don't try to write *for* people; you help them write
|
|
78412
78437
|
*better*. You know the difference, and you respect the difference.
|
|
78413
78438
|
|
|
78439
|
+
Writing is your craft home, not your fence. You're a full Perch operator: when
|
|
78440
|
+
the user needs analysis, files, data, an audit, or delivery, you do that work
|
|
78441
|
+
yourself with the same tools \u2014 in your own voice. You never punt real work to
|
|
78442
|
+
another persona.
|
|
78443
|
+
|
|
78414
78444
|
## How you work
|
|
78415
78445
|
|
|
78416
78446
|
Read first. Before you draft a single sentence, you know what the piece is for,
|
|
@@ -80878,6 +80908,7 @@ var init_personaRegistry = __esm({
|
|
|
80878
80908
|
label: "Saffron",
|
|
80879
80909
|
shortDescription: "Operator. Direct, sharp, finds the thing.",
|
|
80880
80910
|
identityPrompt: SAFFRON_CORE_IDENTITY,
|
|
80911
|
+
styleContract: SAFFRON_STYLE_CONTRACT,
|
|
80881
80912
|
suggestedPrompts: [
|
|
80882
80913
|
"Audit the AP folder for duplicates and anomalies",
|
|
80883
80914
|
"Reconcile the GL against last month's subledger",
|
|
@@ -80901,6 +80932,7 @@ var init_personaRegistry = __esm({
|
|
|
80901
80932
|
label: "Quill",
|
|
80902
80933
|
shortDescription: "Writing companion. Warm, craft-aware, reads first.",
|
|
80903
80934
|
identityPrompt: QUILL_CORE_IDENTITY,
|
|
80935
|
+
styleContract: QUILL_STYLE_CONTRACT,
|
|
80904
80936
|
suggestedPrompts: [
|
|
80905
80937
|
"Draft a memo on personal jurisdiction",
|
|
80906
80938
|
"Brief Pennoyer v. Neff",
|
|
@@ -85443,9 +85475,9 @@ var QUILL_SPECIALIST_AGENTS_PROMPT;
|
|
|
85443
85475
|
var init_quillSpecialistPrompt = __esm({
|
|
85444
85476
|
"features/perchTerminal/agentPlatform/quillSpecialistPrompt.ts"() {
|
|
85445
85477
|
QUILL_SPECIALIST_AGENTS_PROMPT = `
|
|
85446
|
-
##
|
|
85478
|
+
## Writing & Research \u2014 Specialist Delegation
|
|
85447
85479
|
|
|
85448
|
-
|
|
85480
|
+
This task is writing/research/legal shaped. You write and deliver in your own loop, dispatching specialists ONLY for research/verification fan-out \u2014 never for delivery.
|
|
85449
85481
|
|
|
85450
85482
|
Use specialists (dispatch_agent) when independent research or verification benefits from separation:
|
|
85451
85483
|
- general_writer: drafts polished letters, memos, essays, emails, and revisions from the user's request plus provided context. It does NOT research \u2014 only give it general_writer work once you already have the sources/facts it needs.
|
|
@@ -85483,7 +85515,6 @@ Success rules:
|
|
|
85483
85515
|
- Do not say a Google Doc was created or an email was sent unless you have a verified receipt (URL / sent confirmation) or a screenshot showing the result.
|
|
85484
85516
|
- Safety: never click anything that grants access or changes sharing/permissions, such as "Share & send", "Share", or "Grant access". If a share/permission dialog appears, choose the option that sends without changing permissions, such as "Send without sharing" or "Send anyway".
|
|
85485
85517
|
- If delivery hits a problem, recover in the same loop. Try the next reasonable path, or ask the user for permission/choice; do not silently stop or skip the requested delivery. Never claim success without proof.
|
|
85486
|
-
- If the user asks for finance, AP, payroll, KYC, market, meeting, or close work, suggest Saffron unless the task is purely writing the prose.
|
|
85487
85518
|
`.trim();
|
|
85488
85519
|
}
|
|
85489
85520
|
});
|
|
@@ -91884,6 +91915,7 @@ function buildCoreSystemSection(input) {
|
|
|
91884
91915
|
].join("\n")
|
|
91885
91916
|
);
|
|
91886
91917
|
}
|
|
91918
|
+
lines.push("", persona.styleContract);
|
|
91887
91919
|
return lines.join("\n");
|
|
91888
91920
|
}
|
|
91889
91921
|
function appendAgentsModeGuidance(input) {
|
|
@@ -91916,9 +91948,8 @@ function appendAgentsModeGuidance(input) {
|
|
|
91916
91948
|
});
|
|
91917
91949
|
return;
|
|
91918
91950
|
}
|
|
91919
|
-
const isQuill = personaId === "quill";
|
|
91920
91951
|
lines.push(
|
|
91921
|
-
|
|
91952
|
+
"You're in Agents mode. Get the work done.",
|
|
91922
91953
|
"Execute directly when the task is clear and bounded.",
|
|
91923
91954
|
"For greetings, thanks, quick check-ins, or explanation-only questions, answer directly without tools unless the user asks you to inspect, search, run, create, send, or change something.",
|
|
91924
91955
|
"For delivery, write/send/change, filesystem, browser, or other external action turns, call the appropriate tool in the same turn and never end with only a promise, preamble, apology, or status line. For chat-only drafts, summaries, opinions, or answers, once evidence is available, synthesize directly in chat and stop.",
|
|
@@ -91938,9 +91969,10 @@ function appendAgentsModeGuidance(input) {
|
|
|
91938
91969
|
if (approvedPlanBlock) {
|
|
91939
91970
|
lines.push("", approvedPlanBlock);
|
|
91940
91971
|
}
|
|
91941
|
-
if (
|
|
91972
|
+
if (personaId === "quill" && isWritingResearchIntent(assemblyInput.trimmedInput)) {
|
|
91942
91973
|
lines.push("", QUILL_SPECIALIST_AGENTS_PROMPT);
|
|
91943
|
-
}
|
|
91974
|
+
}
|
|
91975
|
+
if (assemblyInput.coordinatorMode || isFinancialOperatorIntent(assemblyInput.trimmedInput)) {
|
|
91944
91976
|
lines.push("", FINANCIAL_OPERATOR_AGENTS_PROMPT);
|
|
91945
91977
|
}
|
|
91946
91978
|
lines.push("", PLATFORM_DELIVERY_GUIDANCE);
|
|
@@ -92053,6 +92085,11 @@ function isFinancialOperatorIntent(input) {
|
|
|
92053
92085
|
input
|
|
92054
92086
|
);
|
|
92055
92087
|
}
|
|
92088
|
+
function isWritingResearchIntent(input) {
|
|
92089
|
+
return /\b(writ(?:e|ing|ten)|draft|redraft|rewrite|revise|edit|proofread|polish|memo(?:randum)?|essay|letter|brief|motion|paper|article|blog|post|thesis|abstract|summar(?:y|ize|ise)|cit(?:e|ation|ations)|research|sources?|irac|case[-\s]?law|statute|regulation|legal|law\s+review)\b/i.test(
|
|
92090
|
+
input
|
|
92091
|
+
);
|
|
92092
|
+
}
|
|
92056
92093
|
function buildApprovedGeneralPlanBlock(session) {
|
|
92057
92094
|
const approvedPlan = session?.approvedGeneralPlan;
|
|
92058
92095
|
if (!approvedPlan) return null;
|
|
@@ -199912,6 +199949,7 @@ function containsBrowserDeliveryTask(tasks) {
|
|
|
199912
199949
|
var BROWSER_DELIVERY_ROLE_IDS;
|
|
199913
199950
|
var init_browserDeliveryLock = __esm({
|
|
199914
199951
|
"features/perchTerminal/agentPlatform/browserDeliveryLock.ts"() {
|
|
199952
|
+
"use strict";
|
|
199915
199953
|
BROWSER_DELIVERY_ROLE_IDS = /* @__PURE__ */ new Set([
|
|
199916
199954
|
"doc_writer",
|
|
199917
199955
|
"email_sender",
|
|
@@ -202537,8 +202575,6 @@ async function dispatchAgentHandler(args, ctx) {
|
|
|
202537
202575
|
parentToolCallId: ctx.parentToolCallId,
|
|
202538
202576
|
mcpTools: ctx.mcpTools ?? []
|
|
202539
202577
|
};
|
|
202540
|
-
const isQuillNormalTurn = ctx.personaId === "quill" && ctx.chatMode !== "coordinator" && !ctx.allowedCallableAgents?.length;
|
|
202541
|
-
void isQuillNormalTurn;
|
|
202542
202578
|
if (Array.isArray(args.tasks) && args.tasks.length > 0) {
|
|
202543
202579
|
const tasks = args.tasks.map(
|
|
202544
202580
|
(t) => ({
|
|
@@ -207292,7 +207328,7 @@ var init_sendWorkerMessage2 = __esm({
|
|
|
207292
207328
|
});
|
|
207293
207329
|
|
|
207294
207330
|
// features/perchTerminal/runtime/toolSystem/tools/workers/spawnWorker.ts
|
|
207295
|
-
var
|
|
207331
|
+
var spawnWorkerTool;
|
|
207296
207332
|
var init_spawnWorker2 = __esm({
|
|
207297
207333
|
"features/perchTerminal/runtime/toolSystem/tools/workers/spawnWorker.ts"() {
|
|
207298
207334
|
"use strict";
|
|
@@ -207301,11 +207337,6 @@ var init_spawnWorker2 = __esm({
|
|
|
207301
207337
|
init_agentDispatch();
|
|
207302
207338
|
init_localScope();
|
|
207303
207339
|
init_toolNames();
|
|
207304
|
-
QUILL_BLOCKED_DELIVERY_WORKER_IDS = /* @__PURE__ */ new Set([
|
|
207305
|
-
"doc_writer",
|
|
207306
|
-
"email_sender",
|
|
207307
|
-
"calendar_scheduler"
|
|
207308
|
-
]);
|
|
207309
207340
|
spawnWorkerTool = {
|
|
207310
207341
|
name: TOOL_NAMES.spawnWorker,
|
|
207311
207342
|
classification: { native: false },
|
|
@@ -207323,8 +207354,6 @@ var init_spawnWorker2 = __esm({
|
|
|
207323
207354
|
errorCode: "worker_event_sink_missing"
|
|
207324
207355
|
};
|
|
207325
207356
|
}
|
|
207326
|
-
if (ctx.personaId === "quill" && ctx.chatMode !== "coordinator" && !ctx.allowedCallableAgents?.length && QUILL_BLOCKED_DELIVERY_WORKER_IDS.has(workerId)) {
|
|
207327
|
-
}
|
|
207328
207357
|
const enrichedContext = await threadPriorSpecialistContext({
|
|
207329
207358
|
threadId: ctx.threadId,
|
|
207330
207359
|
roleId: workerId,
|
|
@@ -220487,52 +220516,15 @@ var init_toolLoop = __esm({
|
|
|
220487
220516
|
}
|
|
220488
220517
|
});
|
|
220489
220518
|
|
|
220490
|
-
// features/perchTerminal/runtime/personas/
|
|
220491
|
-
function isQuillBlockedToolName(toolName) {
|
|
220492
|
-
return QUILL_BLOCKED_TOOL_NAMES.has(toolName);
|
|
220493
|
-
}
|
|
220494
|
-
function filterToolsForQuill(toolDefinitions) {
|
|
220495
|
-
return toolDefinitions.filter((tool) => !isQuillBlockedToolName(tool.function.name));
|
|
220496
|
-
}
|
|
220497
|
-
var QUILL_BLOCKED_TOOL_NAMES;
|
|
220498
|
-
var init_quillToolPolicy = __esm({
|
|
220499
|
-
"features/perchTerminal/runtime/personas/quillToolPolicy.ts"() {
|
|
220500
|
-
"use strict";
|
|
220501
|
-
init_toolNames();
|
|
220502
|
-
QUILL_BLOCKED_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
220503
|
-
TOOL_NAMES.runSuite,
|
|
220504
|
-
TOOL_NAMES.runManagedPlaybook,
|
|
220505
|
-
TOOL_NAMES.listSuiteCatalog,
|
|
220506
|
-
TOOL_NAMES.proposeSuitePlan,
|
|
220507
|
-
TOOL_NAMES.executeSuitePlan,
|
|
220508
|
-
TOOL_NAMES.proposeWork,
|
|
220509
|
-
TOOL_NAMES.executeWork,
|
|
220510
|
-
TOOL_NAMES.generateAPAuditPacket,
|
|
220511
|
-
TOOL_NAMES.safeBrowserAction,
|
|
220512
|
-
// Deprecated non-verified shortcuts — superseded by the verified surface tools.
|
|
220513
|
-
TOOL_NAMES.gmailSendEmail,
|
|
220514
|
-
TOOL_NAMES.gmailSaveDraft,
|
|
220515
|
-
TOOL_NAMES.googleDocsCreate,
|
|
220516
|
-
TOOL_NAMES.googleDocsAppend,
|
|
220517
|
-
TOOL_NAMES.googleCalendarCreateEvent,
|
|
220518
|
-
TOOL_NAMES.googleSheetsCreate,
|
|
220519
|
-
TOOL_NAMES.googleSheetsAppendRows
|
|
220520
|
-
]);
|
|
220521
|
-
}
|
|
220522
|
-
});
|
|
220523
|
-
|
|
220524
|
-
// features/perchTerminal/runtime/personas/saffronToolPolicy.ts
|
|
220519
|
+
// features/perchTerminal/runtime/personas/sharedToolPolicy.ts
|
|
220525
220520
|
function filterSuiteRelayTools(toolDefinitions, opts = {}) {
|
|
220526
220521
|
if (opts.allowSuiteRelay) return toolDefinitions;
|
|
220527
220522
|
return toolDefinitions.filter(
|
|
220528
220523
|
(tool) => !SUITE_RELAY_TOOL_NAMES.has(tool.function.name)
|
|
220529
220524
|
);
|
|
220530
220525
|
}
|
|
220531
|
-
|
|
220532
|
-
|
|
220533
|
-
}
|
|
220534
|
-
var init_saffronToolPolicy = __esm({
|
|
220535
|
-
"features/perchTerminal/runtime/personas/saffronToolPolicy.ts"() {
|
|
220526
|
+
var init_sharedToolPolicy = __esm({
|
|
220527
|
+
"features/perchTerminal/runtime/personas/sharedToolPolicy.ts"() {
|
|
220536
220528
|
"use strict";
|
|
220537
220529
|
init_suiteRelayKillSwitch();
|
|
220538
220530
|
}
|
|
@@ -220650,10 +220642,7 @@ async function runLiveAgentsLoop(input) {
|
|
|
220650
220642
|
PLAN_MODE_ALLOWED_TOOL_NAMES
|
|
220651
220643
|
) : effectiveChatMode === "ask" ? [] : getReadOnlyToolDefinitions(toolOpts);
|
|
220652
220644
|
const suiteRelayEnabled = isSuiteRelayEnabled();
|
|
220653
|
-
const
|
|
220654
|
-
allowSuiteRelay: suiteRelayEnabled
|
|
220655
|
-
}) : baseAgentsTools;
|
|
220656
|
-
const suiteRelayFilteredTools = filterSuiteRelayTools(personaFilteredTools, {
|
|
220645
|
+
const suiteRelayFilteredTools = filterSuiteRelayTools(baseAgentsTools, {
|
|
220657
220646
|
allowSuiteRelay: suiteRelayEnabled
|
|
220658
220647
|
});
|
|
220659
220648
|
const deliveryPolicyDeliveryOperatorOnly = turn.deliveryOperatorOnly === true;
|
|
@@ -221106,8 +221095,7 @@ var init_liveAgentsLoop = __esm({
|
|
|
221106
221095
|
init_toolNames();
|
|
221107
221096
|
init_planModeStateMachine();
|
|
221108
221097
|
init_toolPermissionPolicy();
|
|
221109
|
-
|
|
221110
|
-
init_saffronToolPolicy();
|
|
221098
|
+
init_sharedToolPolicy();
|
|
221111
221099
|
init_deliveryToolPolicy();
|
|
221112
221100
|
init_threadSession();
|
|
221113
221101
|
init_sandboxProvenance();
|
|
@@ -225245,10 +225233,7 @@ async function runOperatorTurn(input, deps) {
|
|
|
225245
225233
|
getEnabledToolDefinitions(toolOpts),
|
|
225246
225234
|
PLAN_MODE_ALLOWED_TOOL_NAMES
|
|
225247
225235
|
) : effectiveChatMode === "agents" ? getExecutableToolDefinitions(toolOpts) : effectiveChatMode === "ask" ? [] : getReadOnlyToolDefinitions(toolOpts);
|
|
225248
|
-
const
|
|
225249
|
-
allowSuiteRelay: suiteRelayEnabled
|
|
225250
|
-
}) : baseTurnToolDefinitions;
|
|
225251
|
-
const suiteRelayFilteredTools = filterSuiteRelayTools(personaFilteredTools, {
|
|
225236
|
+
const suiteRelayFilteredTools = filterSuiteRelayTools(baseTurnToolDefinitions, {
|
|
225252
225237
|
allowSuiteRelay: suiteRelayEnabled
|
|
225253
225238
|
});
|
|
225254
225239
|
const turnToolDefinitions = filterMainPersonaDeliveryTools(suiteRelayFilteredTools, {
|
|
@@ -225936,8 +225921,7 @@ var init_runOperatorTurn = __esm({
|
|
|
225936
225921
|
init_folderIndexing();
|
|
225937
225922
|
init_approvalResume();
|
|
225938
225923
|
init_personaRegistry();
|
|
225939
|
-
|
|
225940
|
-
init_saffronToolPolicy();
|
|
225924
|
+
init_sharedToolPolicy();
|
|
225941
225925
|
init_deliveryToolPolicy();
|
|
225942
225926
|
init_voiceFilters();
|
|
225943
225927
|
init_progressEventBridge();
|
|
@@ -259829,7 +259813,7 @@ var require_websocket = __commonJS({
|
|
|
259829
259813
|
var http2 = __require("http");
|
|
259830
259814
|
var net = __require("net");
|
|
259831
259815
|
var tls = __require("tls");
|
|
259832
|
-
var { randomBytes, createHash:
|
|
259816
|
+
var { randomBytes, createHash: createHash5 } = __require("crypto");
|
|
259833
259817
|
var { Duplex, Readable } = __require("stream");
|
|
259834
259818
|
var { URL: URL2 } = __require("url");
|
|
259835
259819
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -260497,7 +260481,7 @@ var require_websocket = __commonJS({
|
|
|
260497
260481
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
260498
260482
|
return;
|
|
260499
260483
|
}
|
|
260500
|
-
const digest =
|
|
260484
|
+
const digest = createHash5("sha1").update(key + GUID).digest("base64");
|
|
260501
260485
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
260502
260486
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
260503
260487
|
return;
|
|
@@ -260866,7 +260850,7 @@ var require_websocket_server = __commonJS({
|
|
|
260866
260850
|
var EventEmitter3 = __require("events");
|
|
260867
260851
|
var http2 = __require("http");
|
|
260868
260852
|
var { Duplex } = __require("stream");
|
|
260869
|
-
var { createHash:
|
|
260853
|
+
var { createHash: createHash5 } = __require("crypto");
|
|
260870
260854
|
var extension2 = require_extension();
|
|
260871
260855
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
260872
260856
|
var subprotocol2 = require_subprotocol();
|
|
@@ -261173,7 +261157,7 @@ var require_websocket_server = __commonJS({
|
|
|
261173
261157
|
);
|
|
261174
261158
|
}
|
|
261175
261159
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
261176
|
-
const digest =
|
|
261160
|
+
const digest = createHash5("sha1").update(key + GUID).digest("base64");
|
|
261177
261161
|
const headers = [
|
|
261178
261162
|
"HTTP/1.1 101 Switching Protocols",
|
|
261179
261163
|
"Upgrade: websocket",
|
|
@@ -283109,6 +283093,7 @@ __export(perch_cli_exports, {
|
|
|
283109
283093
|
runPerchCli: () => runPerchCli
|
|
283110
283094
|
});
|
|
283111
283095
|
import fs14 from "node:fs";
|
|
283096
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
283112
283097
|
import os5 from "node:os";
|
|
283113
283098
|
import path14 from "node:path";
|
|
283114
283099
|
import readline from "node:readline/promises";
|
|
@@ -283139,7 +283124,8 @@ ${HELP_TEXT}`);
|
|
|
283139
283124
|
const connection = await connectModelProxy({ appUrl: parsed.appUrl });
|
|
283140
283125
|
try {
|
|
283141
283126
|
const threadId = parsed.threadId?.trim() || "cli-default";
|
|
283142
|
-
const
|
|
283127
|
+
const threadScopeKey = await resolveCliThreadScopeKey(connection);
|
|
283128
|
+
const persisted = await hydrateCliThreadState(threadId, threadScopeKey);
|
|
283143
283129
|
const hostedContext = await fetchCliHostedContext(connection, {
|
|
283144
283130
|
query: parsed.prompt,
|
|
283145
283131
|
threadId
|
|
@@ -283181,6 +283167,7 @@ ${HELP_TEXT}`);
|
|
|
283181
283167
|
});
|
|
283182
283168
|
await persistCliThreadState({
|
|
283183
283169
|
threadId: result3.threadId,
|
|
283170
|
+
threadScopeKey,
|
|
283184
283171
|
recentMessages: nextRecentMessages,
|
|
283185
283172
|
contextSnapshot: result3.contextSnapshot ?? persisted?.contextSnapshot ?? null
|
|
283186
283173
|
});
|
|
@@ -283455,14 +283442,15 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
283455
283442
|
terminal: true
|
|
283456
283443
|
});
|
|
283457
283444
|
const state = createInteractiveCliState(options);
|
|
283458
|
-
await hydrateInteractiveCliState(state);
|
|
283459
283445
|
const connectModelProxy = deps.connectModelProxy ?? connectCliModelProxy;
|
|
283460
283446
|
let connection = await connectModelProxy({ appUrl: state.appUrl });
|
|
283461
283447
|
if (!state.appUrl && connection.appUrl) state.appUrl = connection.appUrl;
|
|
283448
|
+
await syncInteractiveCliThreadScope(state, connection, { force: true });
|
|
283462
283449
|
const reconnect = async () => {
|
|
283463
283450
|
connection.restore();
|
|
283464
283451
|
connection = await connectModelProxy({ appUrl: state.appUrl });
|
|
283465
283452
|
if (!state.appUrl && connection.appUrl) state.appUrl = connection.appUrl;
|
|
283453
|
+
await syncInteractiveCliThreadScope(state, connection);
|
|
283466
283454
|
};
|
|
283467
283455
|
writer.stdout(renderInteractiveStartup(state, connection));
|
|
283468
283456
|
try {
|
|
@@ -283548,14 +283536,15 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
283548
283536
|
init_build2().then(() => build_exports)
|
|
283549
283537
|
]);
|
|
283550
283538
|
const state = createInteractiveCliState(options);
|
|
283551
|
-
await hydrateInteractiveCliState(state);
|
|
283552
283539
|
const connectModelProxy = deps.connectModelProxy ?? connectCliModelProxy;
|
|
283553
283540
|
let connection = await connectModelProxy({ appUrl: state.appUrl });
|
|
283554
283541
|
if (!state.appUrl && connection.appUrl) state.appUrl = connection.appUrl;
|
|
283542
|
+
await syncInteractiveCliThreadScope(state, connection, { force: true });
|
|
283555
283543
|
const reconnect = async () => {
|
|
283556
283544
|
connection.restore();
|
|
283557
283545
|
connection = await connectModelProxy({ appUrl: state.appUrl });
|
|
283558
283546
|
if (!state.appUrl && connection.appUrl) state.appUrl = connection.appUrl;
|
|
283547
|
+
await syncInteractiveCliThreadScope(state, connection);
|
|
283559
283548
|
};
|
|
283560
283549
|
const runTurn = deps.runCliTurn ?? runPerchCliTurn;
|
|
283561
283550
|
const instance = Ink2.render(
|
|
@@ -283848,7 +283837,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
283848
283837
|
text: `bash \xB7 ${truncateMiddle(commandText, 54)} \xB7 running`,
|
|
283849
283838
|
tone: "muted",
|
|
283850
283839
|
detailLines: [
|
|
283851
|
-
{ tone: "command", text: `$ ${commandText}
|
|
283840
|
+
{ tone: "command", text: `$ ${commandText}`, language: "bash" },
|
|
283852
283841
|
{ tone: "meta", text: `cwd ${event.cwd || "."}` }
|
|
283853
283842
|
],
|
|
283854
283843
|
expanded: false
|
|
@@ -283896,7 +283885,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
283896
283885
|
richToolIds.current.add(itemId2);
|
|
283897
283886
|
const commandText = renderCommandLine(event.command, event.args);
|
|
283898
283887
|
const details = [
|
|
283899
|
-
{ tone: "command", text: `$ ${commandText}
|
|
283888
|
+
{ tone: "command", text: `$ ${commandText}`, language: "bash" },
|
|
283900
283889
|
{ tone: "meta", text: `cwd ${event.cwd || "."}` },
|
|
283901
283890
|
...outputChunkToDetailLines(event.stdout, "stdout"),
|
|
283902
283891
|
...outputChunkToDetailLines(event.stderr, "stderr")
|
|
@@ -283929,9 +283918,10 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
283929
283918
|
detailLines: [
|
|
283930
283919
|
{
|
|
283931
283920
|
tone: "command",
|
|
283932
|
-
text: event.language === "shell" ? `$ ${event.command}` : `${event.language} cell
|
|
283921
|
+
text: event.language === "shell" ? `$ ${event.command}` : `${event.language} cell`,
|
|
283922
|
+
language: event.language === "shell" ? "bash" : void 0
|
|
283933
283923
|
},
|
|
283934
|
-
...event.language === "shell" ? [] : codePreviewDetailLines(event.command)
|
|
283924
|
+
...event.language === "shell" ? [] : codePreviewDetailLines(event.command, cliLanguageForSandbox(event.language))
|
|
283935
283925
|
],
|
|
283936
283926
|
expanded: false
|
|
283937
283927
|
});
|
|
@@ -284124,14 +284114,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
284124
284114
|
React11.createElement(
|
|
284125
284115
|
Ink2.Box,
|
|
284126
284116
|
{ flexGrow: 1 },
|
|
284127
|
-
React11
|
|
284128
|
-
Ink2.Text,
|
|
284129
|
-
{
|
|
284130
|
-
color: colorForInkDetailTone(line.tone),
|
|
284131
|
-
dimColor: line.tone === "meta"
|
|
284132
|
-
},
|
|
284133
|
-
formatInkDetailLine(line)
|
|
284134
|
-
)
|
|
284117
|
+
renderInkDetailContent(React11, Ink2, line)
|
|
284135
284118
|
)
|
|
284136
284119
|
);
|
|
284137
284120
|
const renderTranscriptItem = (item, index) => {
|
|
@@ -284362,7 +284345,7 @@ async function runInteractiveSlashCommand(input) {
|
|
|
284362
284345
|
input.state.contextSnapshot = null;
|
|
284363
284346
|
input.state.persistedThreadUpdatedAt = null;
|
|
284364
284347
|
clearThreadSession(input.state.threadId);
|
|
284365
|
-
await deleteCliThreadState(input.state.threadId);
|
|
284348
|
+
await deleteCliThreadState(input.state.threadId, input.state.threadScopeKey);
|
|
284366
284349
|
writeModeLine(input.writer, "memory", "cleared");
|
|
284367
284350
|
return "continue";
|
|
284368
284351
|
case "login":
|
|
@@ -284561,6 +284544,7 @@ function createInteractiveCliState(options) {
|
|
|
284561
284544
|
personaId: options.personaId ?? "saffron",
|
|
284562
284545
|
permissionMode: options.permissionMode ?? "default",
|
|
284563
284546
|
threadId: options.threadId?.trim() || "cli-default",
|
|
284547
|
+
threadScopeKey: null,
|
|
284564
284548
|
desktopConnected: options.desktopConnected ?? false,
|
|
284565
284549
|
cliLocalTools: options.cliLocalTools ?? true,
|
|
284566
284550
|
appUrl: resolveCliAppUrl(options.appUrl ?? null, null),
|
|
@@ -284570,14 +284554,25 @@ function createInteractiveCliState(options) {
|
|
|
284570
284554
|
};
|
|
284571
284555
|
}
|
|
284572
284556
|
async function hydrateInteractiveCliState(state) {
|
|
284573
|
-
const persisted = await hydrateCliThreadState(state.threadId);
|
|
284557
|
+
const persisted = await hydrateCliThreadState(state.threadId, state.threadScopeKey);
|
|
284574
284558
|
if (!persisted) return;
|
|
284575
284559
|
state.recentMessages = persisted.recentMessages;
|
|
284576
284560
|
state.contextSnapshot = persisted.contextSnapshot;
|
|
284577
284561
|
state.persistedThreadUpdatedAt = persisted.updatedAt;
|
|
284578
284562
|
}
|
|
284579
|
-
async function
|
|
284580
|
-
const
|
|
284563
|
+
async function syncInteractiveCliThreadScope(state, connection, options = {}) {
|
|
284564
|
+
const nextScopeKey = await resolveCliThreadScopeKey(connection);
|
|
284565
|
+
if (!options.force && state.threadScopeKey === nextScopeKey) return;
|
|
284566
|
+
state.threadScopeKey = nextScopeKey;
|
|
284567
|
+
state.recentMessages = [];
|
|
284568
|
+
state.contextSnapshot = null;
|
|
284569
|
+
state.persistedThreadUpdatedAt = null;
|
|
284570
|
+
clearThreadSession(state.threadId);
|
|
284571
|
+
await hydrateInteractiveCliState(state);
|
|
284572
|
+
}
|
|
284573
|
+
async function hydrateCliThreadState(threadId, threadScopeKey) {
|
|
284574
|
+
const persisted = await readCliThreadState(threadId, threadScopeKey);
|
|
284575
|
+
clearThreadSession(threadId);
|
|
284581
284576
|
if (!persisted) return null;
|
|
284582
284577
|
if (persisted.threadSession) {
|
|
284583
284578
|
await saveThreadSession(persisted.threadSession);
|
|
@@ -284587,6 +284582,7 @@ async function hydrateCliThreadState(threadId) {
|
|
|
284587
284582
|
async function persistInteractiveCliState(state) {
|
|
284588
284583
|
await persistCliThreadState({
|
|
284589
284584
|
threadId: state.threadId,
|
|
284585
|
+
threadScopeKey: state.threadScopeKey,
|
|
284590
284586
|
recentMessages: state.recentMessages,
|
|
284591
284587
|
contextSnapshot: state.contextSnapshot
|
|
284592
284588
|
});
|
|
@@ -284602,13 +284598,13 @@ async function persistCliThreadState(input) {
|
|
|
284602
284598
|
threadSession,
|
|
284603
284599
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
284604
284600
|
};
|
|
284605
|
-
const filePath = cliThreadStatePath(input.threadId);
|
|
284601
|
+
const filePath = cliThreadStatePath(input.threadId, input.threadScopeKey);
|
|
284606
284602
|
await fs14.promises.mkdir(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
284607
284603
|
await fs14.promises.writeFile(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
|
|
284608
284604
|
}
|
|
284609
|
-
async function readCliThreadState(threadId) {
|
|
284605
|
+
async function readCliThreadState(threadId, threadScopeKey) {
|
|
284610
284606
|
try {
|
|
284611
|
-
const raw = await fs14.promises.readFile(cliThreadStatePath(threadId), "utf8");
|
|
284607
|
+
const raw = await fs14.promises.readFile(cliThreadStatePath(threadId, threadScopeKey), "utf8");
|
|
284612
284608
|
const parsed = JSON.parse(raw);
|
|
284613
284609
|
if (parsed.version !== 1 || parsed.threadId !== threadId) return null;
|
|
284614
284610
|
return {
|
|
@@ -284623,12 +284619,28 @@ async function readCliThreadState(threadId) {
|
|
|
284623
284619
|
return null;
|
|
284624
284620
|
}
|
|
284625
284621
|
}
|
|
284626
|
-
async function deleteCliThreadState(threadId) {
|
|
284627
|
-
await fs14.promises.rm(cliThreadStatePath(threadId), { force: true }).catch(() => void 0);
|
|
284622
|
+
async function deleteCliThreadState(threadId, threadScopeKey) {
|
|
284623
|
+
await fs14.promises.rm(cliThreadStatePath(threadId, threadScopeKey), { force: true }).catch(() => void 0);
|
|
284628
284624
|
}
|
|
284629
|
-
function cliThreadStatePath(threadId) {
|
|
284625
|
+
function cliThreadStatePath(threadId, threadScopeKey) {
|
|
284630
284626
|
const base = process.env.PERCH_CLI_STATE_DIR?.trim() || path14.join(os5.homedir(), ".perch", "threads");
|
|
284631
|
-
return path14.join(base, `${safeCliThreadId(threadId)}.json`);
|
|
284627
|
+
return path14.join(base, safeCliThreadScopeKey(threadScopeKey), `${safeCliThreadId(threadId)}.json`);
|
|
284628
|
+
}
|
|
284629
|
+
async function resolveCliThreadScopeKey(connection) {
|
|
284630
|
+
const session = await readStoredCliAuthSession().catch(() => null);
|
|
284631
|
+
const sessionUsable = isStoredCliAuthSessionUsable(session);
|
|
284632
|
+
const authenticated = Boolean(connection?.authenticated || sessionUsable);
|
|
284633
|
+
const appUrl = connection?.appUrl ?? session?.appUrl ?? "local";
|
|
284634
|
+
const userId = connection?.userId ?? session?.userId ?? null;
|
|
284635
|
+
const email = connection?.email ?? session?.email ?? null;
|
|
284636
|
+
const fallbackToken = authenticated && !userId && !email ? session?.accessToken ?? null : null;
|
|
284637
|
+
const authKey = authenticated ? `user:${userId ?? ""}|email:${email ?? ""}|token:${fallbackToken ? createHash4("sha256").update(fallbackToken).digest("hex").slice(0, 20) : ""}` : "anonymous";
|
|
284638
|
+
const raw = `app:${appUrl}|${authKey}`;
|
|
284639
|
+
const prefix = authenticated ? "user" : "anon";
|
|
284640
|
+
return `${prefix}-${createHash4("sha256").update(raw).digest("hex").slice(0, 20)}`;
|
|
284641
|
+
}
|
|
284642
|
+
function safeCliThreadScopeKey(threadScopeKey) {
|
|
284643
|
+
return (threadScopeKey?.trim() || "anon-local").replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(0, 96);
|
|
284632
284644
|
}
|
|
284633
284645
|
function safeCliThreadId(threadId) {
|
|
284634
284646
|
return threadId.trim().replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(0, 96) || "cli-default";
|
|
@@ -284818,6 +284830,53 @@ function bodyColorForInkTone(tone) {
|
|
|
284818
284830
|
return "#fff8f0";
|
|
284819
284831
|
}
|
|
284820
284832
|
}
|
|
284833
|
+
function renderInkDetailContent(React11, Ink2, line) {
|
|
284834
|
+
const prefix = inkDetailPrefix(line.tone);
|
|
284835
|
+
const baseColor = colorForInkDetailTone(line.tone);
|
|
284836
|
+
const language = normalizeCliDetailLanguage(line.language);
|
|
284837
|
+
const tokens = shouldSyntaxHighlightDetail(line) ? tokenizeCliDetailSyntax(language, line.text) : [{ text: line.text, tone: "plain" }];
|
|
284838
|
+
return React11.createElement(
|
|
284839
|
+
Ink2.Text,
|
|
284840
|
+
null,
|
|
284841
|
+
prefix ? React11.createElement(
|
|
284842
|
+
Ink2.Text,
|
|
284843
|
+
{ color: baseColor, bold: line.tone === "add" || line.tone === "remove" },
|
|
284844
|
+
prefix
|
|
284845
|
+
) : null,
|
|
284846
|
+
...tokens.map(
|
|
284847
|
+
(token, index) => React11.createElement(
|
|
284848
|
+
Ink2.Text,
|
|
284849
|
+
{
|
|
284850
|
+
key: `${line.tone}-${index}-${token.tone}`,
|
|
284851
|
+
color: colorForCliSyntaxTone(token.tone, line.tone),
|
|
284852
|
+
dimColor: line.tone === "meta" || token.tone === "comment"
|
|
284853
|
+
},
|
|
284854
|
+
token.text
|
|
284855
|
+
)
|
|
284856
|
+
)
|
|
284857
|
+
);
|
|
284858
|
+
}
|
|
284859
|
+
function shouldSyntaxHighlightDetail(line) {
|
|
284860
|
+
if (line.tone === "meta" || line.tone === "hunk") return false;
|
|
284861
|
+
if (line.tone === "stdout" || line.tone === "stderr") return false;
|
|
284862
|
+
return Boolean(normalizeCliDetailLanguage(line.language)) || line.tone === "command";
|
|
284863
|
+
}
|
|
284864
|
+
function inkDetailPrefix(tone) {
|
|
284865
|
+
switch (tone) {
|
|
284866
|
+
case "add":
|
|
284867
|
+
return "+ ";
|
|
284868
|
+
case "remove":
|
|
284869
|
+
return "- ";
|
|
284870
|
+
case "stderr":
|
|
284871
|
+
return "! ";
|
|
284872
|
+
case "meta":
|
|
284873
|
+
return "# ";
|
|
284874
|
+
case "stdout":
|
|
284875
|
+
return " ";
|
|
284876
|
+
default:
|
|
284877
|
+
return "";
|
|
284878
|
+
}
|
|
284879
|
+
}
|
|
284821
284880
|
function colorForInkDetailTone(tone) {
|
|
284822
284881
|
switch (tone) {
|
|
284823
284882
|
case "add":
|
|
@@ -284836,21 +284895,168 @@ function colorForInkDetailTone(tone) {
|
|
|
284836
284895
|
return CLI_BRAND.cream;
|
|
284837
284896
|
}
|
|
284838
284897
|
}
|
|
284839
|
-
function
|
|
284840
|
-
|
|
284841
|
-
|
|
284842
|
-
|
|
284843
|
-
|
|
284844
|
-
|
|
284845
|
-
|
|
284846
|
-
|
|
284847
|
-
|
|
284848
|
-
|
|
284849
|
-
|
|
284850
|
-
|
|
284851
|
-
|
|
284852
|
-
|
|
284898
|
+
function colorForCliSyntaxTone(tone, lineTone) {
|
|
284899
|
+
if (lineTone === "remove") {
|
|
284900
|
+
switch (tone) {
|
|
284901
|
+
case "comment":
|
|
284902
|
+
return "#8e6a55";
|
|
284903
|
+
case "string":
|
|
284904
|
+
return "#c48656";
|
|
284905
|
+
case "keyword":
|
|
284906
|
+
case "flag":
|
|
284907
|
+
return CLI_BRAND.bronzeGlint;
|
|
284908
|
+
case "number":
|
|
284909
|
+
return "#c77952";
|
|
284910
|
+
case "operator":
|
|
284911
|
+
return CLI_BRAND.bronzeDeep;
|
|
284912
|
+
default:
|
|
284913
|
+
return CLI_BRAND.bronzeGlint;
|
|
284914
|
+
}
|
|
284915
|
+
}
|
|
284916
|
+
if (lineTone === "add") {
|
|
284917
|
+
switch (tone) {
|
|
284918
|
+
case "comment":
|
|
284919
|
+
return "#7f9586";
|
|
284920
|
+
case "string":
|
|
284921
|
+
return "#b7c989";
|
|
284922
|
+
case "keyword":
|
|
284923
|
+
case "flag":
|
|
284924
|
+
return CLI_BRAND.patinaActive;
|
|
284925
|
+
case "number":
|
|
284926
|
+
return "#8fd19c";
|
|
284927
|
+
case "operator":
|
|
284928
|
+
return "#7e9f87";
|
|
284929
|
+
default:
|
|
284930
|
+
return CLI_BRAND.cream;
|
|
284931
|
+
}
|
|
284932
|
+
}
|
|
284933
|
+
if (lineTone === "command") {
|
|
284934
|
+
switch (tone) {
|
|
284935
|
+
case "comment":
|
|
284936
|
+
return "#7a6f66";
|
|
284937
|
+
case "string":
|
|
284938
|
+
case "path":
|
|
284939
|
+
return "#e5bc75";
|
|
284940
|
+
case "keyword":
|
|
284941
|
+
return CLI_BRAND.bronzeGlint;
|
|
284942
|
+
case "flag":
|
|
284943
|
+
return CLI_BRAND.patinaActive;
|
|
284944
|
+
case "number":
|
|
284945
|
+
return "#8fd19c";
|
|
284946
|
+
case "operator":
|
|
284947
|
+
return CLI_BRAND.muted;
|
|
284948
|
+
default:
|
|
284949
|
+
return CLI_BRAND.cream;
|
|
284950
|
+
}
|
|
284853
284951
|
}
|
|
284952
|
+
return colorForInkDetailTone(lineTone);
|
|
284953
|
+
}
|
|
284954
|
+
function tokenizeCliDetailSyntax(language, text) {
|
|
284955
|
+
const normalized = language ?? "text";
|
|
284956
|
+
const pattern = cliSyntaxPatternForLanguage(normalized);
|
|
284957
|
+
if (!pattern) return [{ text, tone: "plain" }];
|
|
284958
|
+
const tokens = [];
|
|
284959
|
+
let lastIndex = 0;
|
|
284960
|
+
pattern.lastIndex = 0;
|
|
284961
|
+
for (let match = pattern.exec(text); match; match = pattern.exec(text)) {
|
|
284962
|
+
if (match.index > lastIndex) {
|
|
284963
|
+
tokens.push({ text: text.slice(lastIndex, match.index), tone: "plain" });
|
|
284964
|
+
}
|
|
284965
|
+
tokens.push({ text: match[0], tone: cliToneForSyntaxToken(normalized, match[0]) });
|
|
284966
|
+
lastIndex = match.index + match[0].length;
|
|
284967
|
+
}
|
|
284968
|
+
if (lastIndex < text.length) {
|
|
284969
|
+
tokens.push({ text: text.slice(lastIndex), tone: "plain" });
|
|
284970
|
+
}
|
|
284971
|
+
return tokens.length ? tokens : [{ text, tone: "plain" }];
|
|
284972
|
+
}
|
|
284973
|
+
function cliSyntaxPatternForLanguage(language) {
|
|
284974
|
+
if (["javascript", "typescript", "jsx", "tsx"].includes(language)) {
|
|
284975
|
+
return /(\/\/.*$|\/\*.*?\*\/|`(?:\\.|[^`])*`|'(?:\\.|[^'])*'|"(?:\\.|[^"])*"|\b(?:const|let|var|function|return|if|else|for|while|import|export|from|class|extends|new|await|async|try|catch|throw|type|interface|implements|switch|case|break|continue|null|undefined|true|false)\b|\b\d+(?:\.\d+)?\b|=>|===|!==|==|!=|\|\||&&|[{}()[\].,:;<>/+*=-])/g;
|
|
284976
|
+
}
|
|
284977
|
+
if (language === "python") {
|
|
284978
|
+
return /(#.*$|'''[\s\S]*?'''|"""[\s\S]*?"""|'(?:\\.|[^'])*'|"(?:\\.|[^"])*"|\b(?:def|class|return|if|elif|else|for|while|import|from|as|try|except|raise|with|yield|lambda|None|True|False|async|await|pass|break|continue|and|or|not)\b|\b\d+(?:\.\d+)?\b|[{}()[\].,:;<>/+*=-])/g;
|
|
284979
|
+
}
|
|
284980
|
+
if (language === "bash" || language === "shell") {
|
|
284981
|
+
return /(#.*$|'[^']*'|"(?:\\.|[^"])*"|\$(?:\w+|\{[^}]+\})|(?:^|\s)-{1,2}[A-Za-z0-9][\w-]*|\b(?:if|then|fi|for|do|done|case|esac|function|export|local|sudo|cd|echo|grep|sed|awk|find|cat|ls|mkdir|rm|mv|cp|npm|node|tsx|python|git|rg)\b|\b\d+\b|[|&;()<>])/g;
|
|
284982
|
+
}
|
|
284983
|
+
if (language === "json" || language === "yaml") {
|
|
284984
|
+
return /("(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|\b(?:true|false|null)\b|\b\d+(?:\.\d+)?\b|[:{}\[\],-])/g;
|
|
284985
|
+
}
|
|
284986
|
+
if (language === "css") {
|
|
284987
|
+
return /(\/\*.*?\*\/|"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|#[0-9a-fA-F]{3,8}\b|\b\d+(?:\.\d+)?(?:px|rem|em|%)?\b|[{}:;(),])/g;
|
|
284988
|
+
}
|
|
284989
|
+
if (language === "html" || language === "xml") {
|
|
284990
|
+
return /(<!--.*?-->|<\/?[A-Za-z0-9:-]+|"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|\/?>)/g;
|
|
284991
|
+
}
|
|
284992
|
+
return null;
|
|
284993
|
+
}
|
|
284994
|
+
function cliToneForSyntaxToken(language, text) {
|
|
284995
|
+
if (text.startsWith("//") || text.startsWith("/*") || text.startsWith("#") || text.startsWith("<!--")) {
|
|
284996
|
+
return "comment";
|
|
284997
|
+
}
|
|
284998
|
+
if (text.startsWith("'") || text.startsWith('"') || text.startsWith("`")) {
|
|
284999
|
+
return language === "json" && text.endsWith(":") ? "keyword" : "string";
|
|
285000
|
+
}
|
|
285001
|
+
if (/^\s*-{1,2}[A-Za-z0-9]/.test(text)) return "flag";
|
|
285002
|
+
if (/^\$/.test(text)) return "keyword";
|
|
285003
|
+
if (/^\d/.test(text)) return "number";
|
|
285004
|
+
if (/^[{}()[\].,:;<>/+*=\-|&]+$/.test(text)) return "operator";
|
|
285005
|
+
if (/[\\/]/.test(text) && !/\s/.test(text)) return "path";
|
|
285006
|
+
return "keyword";
|
|
285007
|
+
}
|
|
285008
|
+
function normalizeCliDetailLanguage(language) {
|
|
285009
|
+
const value = language?.trim().toLowerCase();
|
|
285010
|
+
if (!value) return null;
|
|
285011
|
+
const map2 = {
|
|
285012
|
+
js: "javascript",
|
|
285013
|
+
mjs: "javascript",
|
|
285014
|
+
cjs: "javascript",
|
|
285015
|
+
jsx: "jsx",
|
|
285016
|
+
ts: "typescript",
|
|
285017
|
+
mts: "typescript",
|
|
285018
|
+
cts: "typescript",
|
|
285019
|
+
tsx: "tsx",
|
|
285020
|
+
py: "python",
|
|
285021
|
+
python3: "python",
|
|
285022
|
+
sh: "bash",
|
|
285023
|
+
shell: "bash",
|
|
285024
|
+
zsh: "bash",
|
|
285025
|
+
yml: "yaml",
|
|
285026
|
+
htm: "html"
|
|
285027
|
+
};
|
|
285028
|
+
return map2[value] ?? value;
|
|
285029
|
+
}
|
|
285030
|
+
function cliLanguageForSandbox(language) {
|
|
285031
|
+
if (language === "node") return "javascript";
|
|
285032
|
+
if (language === "shell") return "bash";
|
|
285033
|
+
return normalizeCliDetailLanguage(language) ?? "text";
|
|
285034
|
+
}
|
|
285035
|
+
function inferCliLanguageFromPath(filePath) {
|
|
285036
|
+
const extension2 = filePath.split(/[./\\]/).pop()?.toLowerCase();
|
|
285037
|
+
if (!extension2 || extension2 === filePath.toLowerCase()) return null;
|
|
285038
|
+
const map2 = {
|
|
285039
|
+
js: "javascript",
|
|
285040
|
+
mjs: "javascript",
|
|
285041
|
+
cjs: "javascript",
|
|
285042
|
+
jsx: "jsx",
|
|
285043
|
+
ts: "typescript",
|
|
285044
|
+
mts: "typescript",
|
|
285045
|
+
cts: "typescript",
|
|
285046
|
+
tsx: "tsx",
|
|
285047
|
+
py: "python",
|
|
285048
|
+
sh: "bash",
|
|
285049
|
+
zsh: "bash",
|
|
285050
|
+
json: "json",
|
|
285051
|
+
jsonc: "json",
|
|
285052
|
+
yml: "yaml",
|
|
285053
|
+
yaml: "yaml",
|
|
285054
|
+
css: "css",
|
|
285055
|
+
html: "html",
|
|
285056
|
+
htm: "html",
|
|
285057
|
+
xml: "xml"
|
|
285058
|
+
};
|
|
285059
|
+
return map2[extension2] ?? null;
|
|
284854
285060
|
}
|
|
284855
285061
|
function buildFileToolDisplay(toolName, input, phase, summary) {
|
|
284856
285062
|
const normalizedName = toolName.toLowerCase();
|
|
@@ -284861,12 +285067,13 @@ function buildFileToolDisplay(toolName, input, phase, summary) {
|
|
|
284861
285067
|
const filePath = stringValue8(input.path) ?? stringValue8(input.filePath) ?? summary?.filePath ?? "file";
|
|
284862
285068
|
const short = shortFilePath(filePath);
|
|
284863
285069
|
const status = phase === "running" ? "running" : summary?.changeKind ?? "done";
|
|
285070
|
+
const language = inferCliLanguageFromPath(filePath);
|
|
284864
285071
|
if (isWrite) {
|
|
284865
285072
|
const content = stringValue8(input.content) ?? "";
|
|
284866
285073
|
const added = summary?.linesAdded ?? countTextLines(content);
|
|
284867
285074
|
const detailLines = [
|
|
284868
285075
|
{ tone: "hunk", text: `@@ ${short}` },
|
|
284869
|
-
...textToDetailLines(content, "add")
|
|
285076
|
+
...textToDetailLines(content, "add", language)
|
|
284870
285077
|
];
|
|
284871
285078
|
if (detailLines.length === 1 && summary) {
|
|
284872
285079
|
detailLines.push({ tone: "meta", text: describeChangeSummary(summary) });
|
|
@@ -284883,8 +285090,8 @@ function buildFileToolDisplay(toolName, input, phase, summary) {
|
|
|
284883
285090
|
const added = summary?.linesAdded ?? countTextLines(newText);
|
|
284884
285091
|
const detailLines = [
|
|
284885
285092
|
{ tone: "hunk", text: `@@ ${short}` },
|
|
284886
|
-
...textToDetailLines(oldText, "remove"),
|
|
284887
|
-
...textToDetailLines(newText, "add")
|
|
285093
|
+
...textToDetailLines(oldText, "remove", language),
|
|
285094
|
+
...textToDetailLines(newText, "add", language)
|
|
284888
285095
|
];
|
|
284889
285096
|
if (detailLines.length === 1 && summary) {
|
|
284890
285097
|
detailLines.push({ tone: "meta", text: describeChangeSummary(summary) });
|
|
@@ -284910,18 +285117,23 @@ function describeChangeSummary(summary) {
|
|
|
284910
285117
|
const removed = summary.linesRemoved ?? 0;
|
|
284911
285118
|
return `${kind} \xB7 +${added} -${removed}`;
|
|
284912
285119
|
}
|
|
284913
|
-
function textToDetailLines(text, tone) {
|
|
285120
|
+
function textToDetailLines(text, tone, language) {
|
|
284914
285121
|
if (!text) return [];
|
|
284915
285122
|
const lines = text.split(/\r?\n/);
|
|
284916
|
-
const
|
|
285123
|
+
const normalizedLanguage = normalizeCliDetailLanguage(language);
|
|
285124
|
+
const preview = lines.slice(0, 38).map((line) => ({
|
|
285125
|
+
tone,
|
|
285126
|
+
text: line,
|
|
285127
|
+
...normalizedLanguage ? { language: normalizedLanguage } : {}
|
|
285128
|
+
}));
|
|
284917
285129
|
if (lines.length > preview.length) {
|
|
284918
285130
|
preview.push({ tone: "meta", text: `${lines.length - preview.length} more line(s)` });
|
|
284919
285131
|
}
|
|
284920
285132
|
return preview;
|
|
284921
285133
|
}
|
|
284922
|
-
function codePreviewDetailLines(code) {
|
|
285134
|
+
function codePreviewDetailLines(code, language) {
|
|
284923
285135
|
if (!code.trim()) return [];
|
|
284924
|
-
return textToDetailLines(code, "command").slice(0, 18);
|
|
285136
|
+
return textToDetailLines(code, "command", language).slice(0, 18);
|
|
284925
285137
|
}
|
|
284926
285138
|
function outputChunkToDetailLines(text, tone) {
|
|
284927
285139
|
if (!text) return [];
|