wave-agent-sdk 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +9 -20
- package/dist/agent.js +35 -97
- package/dist/managers/aiManager.d.ts +63 -8
- package/dist/managers/aiManager.js +274 -80
- package/dist/managers/messageManager.d.ts +9 -5
- package/dist/managers/messageManager.js +36 -12
- package/dist/managers/permissionManager.js +13 -11
- package/dist/managers/subagentManager.d.ts +6 -0
- package/dist/managers/subagentManager.js +33 -22
- package/dist/prompts/index.d.ts +0 -2
- package/dist/prompts/index.js +71 -47
- package/dist/services/aiService.d.ts +1 -34
- package/dist/services/aiService.js +18 -130
- package/dist/services/autoMemoryService.d.ts +27 -2
- package/dist/services/autoMemoryService.js +124 -36
- package/dist/services/configurationService.js +14 -1
- package/dist/services/session.d.ts +13 -0
- package/dist/services/session.js +64 -0
- package/dist/types/agent.d.ts +0 -2
- package/dist/types/config.d.ts +7 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/utils/containerSetup.js +12 -3
- package/package.json +1 -1
- package/src/agent.ts +50 -110
- package/src/managers/aiManager.ts +370 -105
- package/src/managers/messageManager.ts +51 -23
- package/src/managers/permissionManager.ts +15 -13
- package/src/managers/subagentManager.ts +36 -25
- package/src/prompts/index.ts +75 -56
- package/src/services/aiService.ts +25 -203
- package/src/services/autoMemoryService.ts +145 -39
- package/src/services/configurationService.ts +16 -1
- package/src/services/session.ts +68 -0
- package/src/types/agent.ts +0 -6
- package/src/types/config.ts +7 -0
- package/src/types/core.ts +1 -1
- package/src/utils/containerSetup.ts +12 -4
- package/dist/constants/goalPrompts.d.ts +0 -1
- package/dist/constants/goalPrompts.js +0 -10
- package/dist/managers/goalManager.d.ts +0 -42
- package/dist/managers/goalManager.js +0 -177
- package/src/constants/goalPrompts.ts +0 -10
- package/src/managers/goalManager.ts +0 -232
|
@@ -37,7 +37,6 @@ import { READ_TOOL_NAME } from "../constants/tools.js";
|
|
|
37
37
|
import { Container } from "../utils/container.js";
|
|
38
38
|
|
|
39
39
|
export interface MessageManagerCallbacks {
|
|
40
|
-
onMessagesChange?: (messages: Message[]) => void;
|
|
41
40
|
onSessionIdChange?: (sessionId: string) => void;
|
|
42
41
|
onLatestTotalTokensChange?: (latestTotalTokens: number) => void;
|
|
43
42
|
onUsagesChange?: (usages: Usage[]) => void;
|
|
@@ -64,9 +63,17 @@ export interface MessageManagerCallbacks {
|
|
|
64
63
|
onCompactBlockAdded?: (content: string) => void;
|
|
65
64
|
onCompactionStateChange?: (isCompacting: boolean) => void;
|
|
66
65
|
// Bang callback
|
|
67
|
-
onAddBangMessage?: (command: string) => void;
|
|
68
|
-
onUpdateBangMessage?: (
|
|
69
|
-
|
|
66
|
+
onAddBangMessage?: (command: string, messageId: string) => void;
|
|
67
|
+
onUpdateBangMessage?: (
|
|
68
|
+
command: string,
|
|
69
|
+
output: string,
|
|
70
|
+
messageId: string,
|
|
71
|
+
) => void;
|
|
72
|
+
onCompleteBangMessage?: (
|
|
73
|
+
command: string,
|
|
74
|
+
exitCode: number,
|
|
75
|
+
messageId: string,
|
|
76
|
+
) => void;
|
|
70
77
|
onInfoBlockAdded?: (content: string) => void;
|
|
71
78
|
// Rewind callbacks
|
|
72
79
|
onShowRewind?: () => void;
|
|
@@ -322,8 +329,6 @@ export class MessageManager {
|
|
|
322
329
|
this.extractFileReadsFromMessage(messages[messages.length - 1]);
|
|
323
330
|
this.extractSkillInvocationsFromMessage(messages[messages.length - 1]);
|
|
324
331
|
}
|
|
325
|
-
|
|
326
|
-
this.callbacks.onMessagesChange?.([...messages]);
|
|
327
332
|
}
|
|
328
333
|
|
|
329
334
|
/**
|
|
@@ -339,6 +344,20 @@ export class MessageManager {
|
|
|
339
344
|
return;
|
|
340
345
|
}
|
|
341
346
|
|
|
347
|
+
// CC-aligned lazy materialization: when the session file has not been
|
|
348
|
+
// materialized yet (no saved messages) and every unsaved message is a
|
|
349
|
+
// meta message (isMeta: true, e.g. SessionStart hook context), skip
|
|
350
|
+
// persistence. Persisting meta-only sessions would create "0 tokens /
|
|
351
|
+
// No content" ghost entries in the resume list. The meta messages stay
|
|
352
|
+
// in memory and are flushed together with the first real user/assistant
|
|
353
|
+
// message (matches CC's pendingEntries buffering).
|
|
354
|
+
if (
|
|
355
|
+
this.savedMessageCount === 0 &&
|
|
356
|
+
unsavedMessages.every((m) => m.isMeta)
|
|
357
|
+
) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
342
361
|
// Create session if needed (only when we have messages to save)
|
|
343
362
|
if (this.savedMessageCount === 0) {
|
|
344
363
|
// This is the first time saving messages, so create the session
|
|
@@ -622,7 +641,9 @@ export class MessageManager {
|
|
|
622
641
|
command,
|
|
623
642
|
});
|
|
624
643
|
this.setMessages(updatedMessages);
|
|
625
|
-
|
|
644
|
+
// The bang message is appended as the last message
|
|
645
|
+
const messageId = this.messages[this.messages.length - 1]?.id ?? "";
|
|
646
|
+
this.callbacks.onAddBangMessage?.(command, messageId);
|
|
626
647
|
}
|
|
627
648
|
|
|
628
649
|
public updateBangMessage(command: string, output: string): void {
|
|
@@ -632,7 +653,8 @@ export class MessageManager {
|
|
|
632
653
|
output,
|
|
633
654
|
});
|
|
634
655
|
this.setMessages(updatedMessages);
|
|
635
|
-
this.
|
|
656
|
+
const messageId = this.findBangMessageId(command) ?? "";
|
|
657
|
+
this.callbacks.onUpdateBangMessage?.(command, output, messageId);
|
|
636
658
|
}
|
|
637
659
|
|
|
638
660
|
public completeBangMessage(
|
|
@@ -647,7 +669,25 @@ export class MessageManager {
|
|
|
647
669
|
output,
|
|
648
670
|
});
|
|
649
671
|
this.setMessages(updatedMessages);
|
|
650
|
-
this.
|
|
672
|
+
const messageId = this.findBangMessageId(command) ?? "";
|
|
673
|
+
this.callbacks.onCompleteBangMessage?.(command, exitCode, messageId);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Find the message ID of the most recent message containing a bang block
|
|
678
|
+
* for the given command. Bang callbacks do not carry a block ID, so the
|
|
679
|
+
* message is located by matching the command against bang blocks.
|
|
680
|
+
*/
|
|
681
|
+
private findBangMessageId(command: string): string | undefined {
|
|
682
|
+
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
683
|
+
const message = this.messages[i];
|
|
684
|
+
if (message.role !== "user") continue;
|
|
685
|
+
const hasBangBlock = message.blocks?.some(
|
|
686
|
+
(block) => block.type === "bang" && block.command === command,
|
|
687
|
+
);
|
|
688
|
+
if (hasBangBlock) return message.id;
|
|
689
|
+
}
|
|
690
|
+
return undefined;
|
|
651
691
|
}
|
|
652
692
|
|
|
653
693
|
public addNotificationMessage(
|
|
@@ -700,7 +740,6 @@ export class MessageManager {
|
|
|
700
740
|
/**
|
|
701
741
|
* Finalize a streaming block of the given type by setting its stage to "end".
|
|
702
742
|
* Fires the corresponding incremental callback with chunk="" to signal finalization.
|
|
703
|
-
* Does NOT call onMessagesChange — the caller is responsible for that.
|
|
704
743
|
* Returns true if a block was finalized.
|
|
705
744
|
*/
|
|
706
745
|
private finalizeStreamingBlock(
|
|
@@ -807,8 +846,6 @@ export class MessageManager {
|
|
|
807
846
|
});
|
|
808
847
|
|
|
809
848
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
810
|
-
|
|
811
|
-
this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
|
|
812
849
|
}
|
|
813
850
|
|
|
814
851
|
/**
|
|
@@ -871,8 +908,6 @@ export class MessageManager {
|
|
|
871
908
|
accumulated: newAccumulatedReasoning,
|
|
872
909
|
stage: "streaming",
|
|
873
910
|
});
|
|
874
|
-
|
|
875
|
-
this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
|
|
876
911
|
}
|
|
877
912
|
|
|
878
913
|
/**
|
|
@@ -885,15 +920,8 @@ export class MessageManager {
|
|
|
885
920
|
const lastMessage = this.messages[this.messages.length - 1];
|
|
886
921
|
if (lastMessage.role !== "assistant") return;
|
|
887
922
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
lastMessage,
|
|
891
|
-
"reasoning",
|
|
892
|
-
);
|
|
893
|
-
|
|
894
|
-
if (textFinalized || reasoningFinalized) {
|
|
895
|
-
this.callbacks.onMessagesChange?.([...this.messages]);
|
|
896
|
-
}
|
|
923
|
+
this.finalizeStreamingBlock(lastMessage, "text");
|
|
924
|
+
this.finalizeStreamingBlock(lastMessage, "reasoning");
|
|
897
925
|
}
|
|
898
926
|
|
|
899
927
|
/**
|
|
@@ -427,18 +427,8 @@ export class PermissionManager {
|
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
// must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
|
|
433
|
-
if (context.permissionMode === "bypassPermissions") {
|
|
434
|
-
const requiresUserInteraction =
|
|
435
|
-
context.toolName === ASK_USER_QUESTION_TOOL_NAME;
|
|
436
|
-
if (!requiresUserInteraction) {
|
|
437
|
-
return { behavior: "allow" };
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// 1.0 Check worktree safety for Write and Edit tools
|
|
430
|
+
// Check worktree safety for Write and Edit tools — unconditional safety
|
|
431
|
+
// check, applied regardless of permission mode (same as read-before-edit).
|
|
442
432
|
// Support both CLI -w sessions (container-registered) and EnterWorktree mid-session
|
|
443
433
|
// (per-agent WorktreeSession stored in this session's container)
|
|
444
434
|
const worktreeSession = this.container.get<WorktreeSession | null>(
|
|
@@ -503,7 +493,19 @@ export class PermissionManager {
|
|
|
503
493
|
}
|
|
504
494
|
}
|
|
505
495
|
|
|
506
|
-
//
|
|
496
|
+
// If bypassPermissions mode, always allow
|
|
497
|
+
// Exception: tools that require user interaction (e.g. AskUserQuestion)
|
|
498
|
+
// must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
|
|
499
|
+
// Worktree safety check above runs unconditionally, so bypass never skips it.
|
|
500
|
+
if (context.permissionMode === "bypassPermissions") {
|
|
501
|
+
const requiresUserInteraction =
|
|
502
|
+
context.toolName === ASK_USER_QUESTION_TOOL_NAME;
|
|
503
|
+
if (!requiresUserInteraction) {
|
|
504
|
+
return { behavior: "allow" };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// If acceptEdits mode, allow Edit, Write, and mkdir in safe zone
|
|
507
509
|
if (context.permissionMode === "acceptEdits") {
|
|
508
510
|
const autoAcceptedTools = [EDIT_TOOL_NAME, WRITE_TOOL_NAME];
|
|
509
511
|
if (autoAcceptedTools.includes(context.toolName)) {
|
|
@@ -790,6 +790,7 @@ export class SubagentManager {
|
|
|
790
790
|
private createSubagentCallbacks(subagentId: string) {
|
|
791
791
|
return {
|
|
792
792
|
onUserMessageAdded: (params: UserMessageParams) => {
|
|
793
|
+
this.refreshSubagentState(subagentId);
|
|
793
794
|
// Forward user message events to parent via SubagentManager callbacks
|
|
794
795
|
if (this.callbacks?.onSubagentUserMessageAdded) {
|
|
795
796
|
this.callbacks.onSubagentUserMessageAdded(subagentId, params);
|
|
@@ -797,6 +798,7 @@ export class SubagentManager {
|
|
|
797
798
|
},
|
|
798
799
|
|
|
799
800
|
onAssistantMessageAdded: (messageId: string) => {
|
|
801
|
+
this.refreshSubagentState(subagentId);
|
|
800
802
|
// Forward assistant message events to parent via SubagentManager callbacks
|
|
801
803
|
if (this.callbacks?.onSubagentAssistantMessageAdded) {
|
|
802
804
|
this.callbacks.onSubagentAssistantMessageAdded(subagentId, messageId);
|
|
@@ -809,6 +811,7 @@ export class SubagentManager {
|
|
|
809
811
|
accumulated: string;
|
|
810
812
|
stage: "streaming" | "end";
|
|
811
813
|
}) => {
|
|
814
|
+
this.refreshSubagentState(subagentId);
|
|
812
815
|
// Forward assistant content updates to parent via SubagentManager callbacks
|
|
813
816
|
if (this.callbacks?.onSubagentAssistantContentUpdated) {
|
|
814
817
|
this.callbacks.onSubagentAssistantContentUpdated({
|
|
@@ -823,6 +826,7 @@ export class SubagentManager {
|
|
|
823
826
|
accumulated: string;
|
|
824
827
|
stage: "streaming" | "end";
|
|
825
828
|
}) => {
|
|
829
|
+
this.refreshSubagentState(subagentId);
|
|
826
830
|
// Forward assistant reasoning updates to parent via SubagentManager callbacks
|
|
827
831
|
if (this.callbacks?.onSubagentAssistantReasoningUpdated) {
|
|
828
832
|
this.callbacks.onSubagentAssistantReasoningUpdated({
|
|
@@ -833,6 +837,7 @@ export class SubagentManager {
|
|
|
833
837
|
},
|
|
834
838
|
|
|
835
839
|
onToolBlockUpdated: (params: ToolBlockUpdateCallbackParams) => {
|
|
840
|
+
this.refreshSubagentState(subagentId);
|
|
836
841
|
const instance = this.instances.get(subagentId);
|
|
837
842
|
if (instance) {
|
|
838
843
|
// Log tool execution to file only when finalized
|
|
@@ -852,31 +857,6 @@ export class SubagentManager {
|
|
|
852
857
|
}
|
|
853
858
|
},
|
|
854
859
|
|
|
855
|
-
// These callbacks will be handled by the parent agent
|
|
856
|
-
onMessagesChange: (messages: Message[]) => {
|
|
857
|
-
const instance = this.instances.get(subagentId);
|
|
858
|
-
if (instance) {
|
|
859
|
-
instance.messages = messages;
|
|
860
|
-
// Compute usedTools from messages (last 2 tool blocks)
|
|
861
|
-
const toolBlocks = messages.flatMap(
|
|
862
|
-
(m) => m.blocks?.filter((b) => b.type === "tool") ?? [],
|
|
863
|
-
);
|
|
864
|
-
const last2 = toolBlocks.slice(-2);
|
|
865
|
-
instance.usedTools = last2.map((tb) => ({
|
|
866
|
-
name: tb.name ?? "",
|
|
867
|
-
parameters: tb.parameters ?? "",
|
|
868
|
-
compactParams: tb.compactParams,
|
|
869
|
-
stage: tb.stage,
|
|
870
|
-
}));
|
|
871
|
-
// Trigger the onUpdate callback if provided
|
|
872
|
-
instance.onUpdate?.();
|
|
873
|
-
// Forward subagent message changes to parent via callbacks
|
|
874
|
-
if (this.callbacks?.onSubagentMessagesChange) {
|
|
875
|
-
this.callbacks.onSubagentMessagesChange(subagentId, messages);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
|
|
880
860
|
onLatestTotalTokensChange: (tokens: number) => {
|
|
881
861
|
const instance = this.instances.get(subagentId);
|
|
882
862
|
if (instance) {
|
|
@@ -890,6 +870,7 @@ export class SubagentManager {
|
|
|
890
870
|
},
|
|
891
871
|
|
|
892
872
|
onErrorBlockAdded: (error: string) => {
|
|
873
|
+
this.refreshSubagentState(subagentId);
|
|
893
874
|
const instance = this.instances.get(subagentId);
|
|
894
875
|
if (instance?.logStream) {
|
|
895
876
|
instance.logStream.write(
|
|
@@ -899,4 +880,34 @@ export class SubagentManager {
|
|
|
899
880
|
},
|
|
900
881
|
};
|
|
901
882
|
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Pull the latest messages from the subagent instance's MessageManager and
|
|
886
|
+
* refresh the instance's cached messages, usedTools, onUpdate and the
|
|
887
|
+
* onSubagentMessagesChange forwarding. Triggered by incremental callbacks.
|
|
888
|
+
*/
|
|
889
|
+
private refreshSubagentState(subagentId: string): void {
|
|
890
|
+
const instance = this.instances.get(subagentId);
|
|
891
|
+
if (!instance) return;
|
|
892
|
+
|
|
893
|
+
const messages = instance.messageManager.getMessages();
|
|
894
|
+
instance.messages = messages;
|
|
895
|
+
// Compute usedTools from messages (last 2 tool blocks)
|
|
896
|
+
const toolBlocks = messages.flatMap(
|
|
897
|
+
(m) => m.blocks?.filter((b) => b.type === "tool") ?? [],
|
|
898
|
+
);
|
|
899
|
+
const last2 = toolBlocks.slice(-2);
|
|
900
|
+
instance.usedTools = last2.map((tb) => ({
|
|
901
|
+
name: tb.name ?? "",
|
|
902
|
+
parameters: tb.parameters ?? "",
|
|
903
|
+
compactParams: tb.compactParams,
|
|
904
|
+
stage: tb.stage,
|
|
905
|
+
}));
|
|
906
|
+
// Trigger the onUpdate callback if provided
|
|
907
|
+
instance.onUpdate?.();
|
|
908
|
+
// Forward subagent message changes to parent via callbacks
|
|
909
|
+
if (this.callbacks?.onSubagentMessagesChange) {
|
|
910
|
+
this.callbacks.onSubagentMessagesChange(subagentId, messages);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
902
913
|
}
|
package/src/prompts/index.ts
CHANGED
|
@@ -346,10 +346,46 @@ export function formatCompactSummary(summary: string): string {
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Notes block prepended to the subagent env section, aligned with Claude
|
|
352
|
+
* Code's enhanceSystemPromptWithEnvDetails().
|
|
353
|
+
*/
|
|
354
|
+
const SUBAGENT_ENV_NOTES = `Notes:
|
|
355
|
+
- Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.
|
|
356
|
+
- In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
|
|
357
|
+
- For clear communication with the user the assistant MUST avoid using emojis.
|
|
358
|
+
- Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Shell info line, aligned with Claude Code's getShellInfoLine(). On win32 an
|
|
362
|
+
* extra Unix-syntax hint is appended.
|
|
363
|
+
*/
|
|
364
|
+
function getShellInfoLine(): string {
|
|
365
|
+
const shell = process.env.SHELL || "unknown";
|
|
366
|
+
const shellName = shell.includes("zsh")
|
|
367
|
+
? "zsh"
|
|
368
|
+
: shell.includes("bash")
|
|
369
|
+
? "bash"
|
|
370
|
+
: shell;
|
|
371
|
+
if (os.platform() === "win32") {
|
|
372
|
+
return `Shell: ${shellName} (use Unix shell syntax, not Windows — e.g., /dev/null not NUL, forward slashes in paths)`;
|
|
373
|
+
}
|
|
374
|
+
return `Shell: ${shellName}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* OS Version value, aligned with Claude Code's getUnameSR(). os.type() and
|
|
379
|
+
* os.release() wrap uname(3) on POSIX, producing output byte-identical to
|
|
380
|
+
* `uname -sr`. Windows has no uname(3); os.type() returns "Windows_NT" there,
|
|
381
|
+
* but os.version() gives the friendlier "Windows 11 Pro", so use that instead.
|
|
382
|
+
*/
|
|
383
|
+
function getUnameSR(): string {
|
|
384
|
+
if (os.platform() === "win32") {
|
|
385
|
+
return `${os.version()} ${os.release()}`;
|
|
386
|
+
}
|
|
387
|
+
return `${os.type()} ${os.release()}`;
|
|
388
|
+
}
|
|
353
389
|
|
|
354
390
|
export function buildSystemPrompt(
|
|
355
391
|
basePrompt: string | undefined,
|
|
@@ -391,29 +427,51 @@ export function buildSystemPrompt(
|
|
|
391
427
|
if (options.workdir) {
|
|
392
428
|
const isGitRepo = isGitRepository(options.workdir);
|
|
393
429
|
const platform = os.platform();
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
const shellName = shell.includes("zsh")
|
|
398
|
-
? "zsh"
|
|
399
|
-
: shell.includes("bash")
|
|
400
|
-
? "bash"
|
|
401
|
-
: shell;
|
|
402
|
-
|
|
430
|
+
const shellInfo = getShellInfoLine();
|
|
431
|
+
const osVersion = getUnameSR();
|
|
432
|
+
const primaryWorkdir = options.originalWorkdir ?? options.workdir;
|
|
403
433
|
const worktreeSession = options.worktreeSession;
|
|
404
434
|
|
|
405
|
-
|
|
435
|
+
if (options.isSubagent) {
|
|
436
|
+
// Subagent env section, aligned with Claude Code's computeEnvInfo() +
|
|
437
|
+
// enhanceSystemPromptWithEnvDetails() (without the model description and
|
|
438
|
+
// knowledge cutoff lines, which Wave does not use).
|
|
439
|
+
dynamicText += `
|
|
440
|
+
|
|
441
|
+
${SUBAGENT_ENV_NOTES}
|
|
406
442
|
|
|
407
443
|
Here is useful information about the environment you are running in:
|
|
408
444
|
<env>
|
|
409
|
-
|
|
445
|
+
Working directory: ${primaryWorkdir}
|
|
410
446
|
Is directory a git repo: ${isGitRepo}
|
|
411
447
|
Platform: ${platform}
|
|
412
|
-
|
|
448
|
+
${shellInfo}
|
|
413
449
|
OS Version: ${osVersion}
|
|
414
|
-
Today's date: ${today}
|
|
415
450
|
</env>
|
|
416
451
|
`;
|
|
452
|
+
} else {
|
|
453
|
+
// Main agent env section, aligned with Claude Code's
|
|
454
|
+
// computeSimpleEnvInfo() (without the model description, knowledge
|
|
455
|
+
// cutoff, and marketing lines, which Wave does not use).
|
|
456
|
+
const envItems = [
|
|
457
|
+
`Primary working directory: ${primaryWorkdir}`,
|
|
458
|
+
worktreeSession
|
|
459
|
+
? `This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root.`
|
|
460
|
+
: null,
|
|
461
|
+
`Is a git repository: ${isGitRepo}`,
|
|
462
|
+
`Platform: ${platform}`,
|
|
463
|
+
shellInfo,
|
|
464
|
+
`OS Version: ${osVersion}`,
|
|
465
|
+
].filter((item): item is string => item !== null);
|
|
466
|
+
|
|
467
|
+
const envBlock = [
|
|
468
|
+
`# Environment`,
|
|
469
|
+
`You have been invoked in the following environment: `,
|
|
470
|
+
...envItems.map((item) => ` - ${item}`),
|
|
471
|
+
].join("\n");
|
|
472
|
+
|
|
473
|
+
dynamicText += `\n\n${envBlock}`;
|
|
474
|
+
}
|
|
417
475
|
}
|
|
418
476
|
|
|
419
477
|
if (options.autoMemory) {
|
|
@@ -429,42 +487,3 @@ Today's date: ${today}
|
|
|
429
487
|
|
|
430
488
|
return blocks;
|
|
431
489
|
}
|
|
432
|
-
|
|
433
|
-
export function enhanceSystemPromptWithEnvDetails(
|
|
434
|
-
existingSystemPrompt: string,
|
|
435
|
-
workdir: string,
|
|
436
|
-
originalWorkdir?: string,
|
|
437
|
-
worktreeSession?: WorktreeSession | null,
|
|
438
|
-
): string {
|
|
439
|
-
const isGitRepo = isGitRepository(workdir);
|
|
440
|
-
const platform = os.platform();
|
|
441
|
-
const osVersion = `${os.type()} ${os.release()}`;
|
|
442
|
-
const today = new Date().toISOString().split("T")[0];
|
|
443
|
-
const shell = process.env.SHELL || "unknown";
|
|
444
|
-
const shellName = shell.includes("zsh")
|
|
445
|
-
? "zsh"
|
|
446
|
-
: shell.includes("bash")
|
|
447
|
-
? "bash"
|
|
448
|
-
: shell;
|
|
449
|
-
|
|
450
|
-
const notes = `Notes:
|
|
451
|
-
- Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.${worktreeSession ? `\n- You are in a git worktree at ${worktreeSession.worktreePath} (branch: ${worktreeSession.worktreeBranch}). Absolute paths from prior context may refer to the original repo at ${worktreeSession.originalCwd}; translate them to your worktree. Do NOT edit files outside this worktree.` : ""}
|
|
452
|
-
- In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
|
|
453
|
-
- For clear communication with the user the assistant MUST avoid using emojis.
|
|
454
|
-
- Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
|
|
455
|
-
|
|
456
|
-
return `${existingSystemPrompt}
|
|
457
|
-
|
|
458
|
-
${notes}
|
|
459
|
-
|
|
460
|
-
Here is useful information about the environment you are running in:
|
|
461
|
-
<env>
|
|
462
|
-
Primary working directory: ${originalWorkdir ?? workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
|
|
463
|
-
Is directory a git repo: ${isGitRepo}
|
|
464
|
-
Platform: ${platform}
|
|
465
|
-
Shell: ${shellName}
|
|
466
|
-
OS Version: ${osVersion}
|
|
467
|
-
Today's date: ${today}
|
|
468
|
-
</env>
|
|
469
|
-
`;
|
|
470
|
-
}
|