@xopcai/xopc 0.0.96 → 0.0.97

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.
Files changed (76) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agents-DmIuSaOE.js → agents-B_YUvNi6.js} +1 -1
  4. package/dist/gateway/static/root/assets/{apps-page-DFcHBxLw.js → apps-page-BmwG5aur.js} +1 -1
  5. package/dist/gateway/static/root/assets/{channels-settings-DDUf55C5.js → channels-settings-BiwkeKPb.js} +1 -1
  6. package/dist/gateway/static/root/assets/{channels-status-swr-BxF-_nzD.js → channels-status-swr-ChyN473C.js} +1 -1
  7. package/dist/gateway/static/root/assets/{cron-api-DylQtnb_.js → cron-api-CvSifIfJ.js} +1 -1
  8. package/dist/gateway/static/root/assets/{cron-page-BO0d9Pf-.js → cron-page-BDqTDFy6.js} +1 -1
  9. package/dist/gateway/static/root/assets/{dist-BskF0qDS.js → dist-DxsUrjpy.js} +1 -1
  10. package/dist/gateway/static/root/assets/{extension-debug-page-BcZdTdjJ.js → extension-debug-page-DV_Av5Jq.js} +1 -1
  11. package/dist/gateway/static/root/assets/{extension-page-D2iuDa1D.js → extension-page-CwZwRhWw.js} +1 -1
  12. package/dist/gateway/static/root/assets/{extension-settings-page-BKpQCgLc.js → extension-settings-page-Bb7TR1Se.js} +1 -1
  13. package/dist/gateway/static/root/assets/{fetch-CtNDpjij.js → fetch-BLLOP2CM.js} +1 -1
  14. package/dist/gateway/static/root/assets/{field-primitives-2PekrGZF.js → field-primitives-CyqVu1QR.js} +1 -1
  15. package/dist/gateway/static/root/assets/{heartbeat-config-api-D3D7SW8A.js → heartbeat-config-api-Cd4M1eHt.js} +1 -1
  16. package/dist/gateway/static/root/assets/{index-Db9fd_X4.js → index-0tS9lV85.js} +5 -5
  17. package/dist/gateway/static/root/assets/index-BJDmBCSl.css +1 -0
  18. package/dist/gateway/static/root/assets/{logs-page-B3I1a26m.js → logs-page-BsAOSowN.js} +1 -1
  19. package/dist/gateway/static/root/assets/{note-detail-page-BOizhtJ8.js → note-detail-page-Dlxoy6Ap.js} +1 -1
  20. package/dist/gateway/static/root/assets/{note-time-CjUGtqKr.js → note-time-B-r8yTpQ.js} +1 -1
  21. package/dist/gateway/static/root/assets/{notes-page-BB8-I0Of.js → notes-page-CHFcyqYW.js} +1 -1
  22. package/dist/gateway/static/root/assets/{sessions-page-BcH-1K9i.js → sessions-page-Ctu0kgt7.js} +1 -1
  23. package/dist/gateway/static/root/assets/{settings-advanced-gate-Czn8nnjN.js → settings-advanced-gate-Dh0TyOOg.js} +1 -1
  24. package/dist/gateway/static/root/assets/{settings-form-section-ZZWDwgVe.js → settings-form-section-DXMCEW1d.js} +1 -1
  25. package/dist/gateway/static/root/assets/{settings-page-BX8c_zrN.js → settings-page-CIkZ7233.js} +1 -1
  26. package/dist/gateway/static/root/assets/{share-preview-page-Ch3_6Qah.js → share-preview-page-7RV65xhJ.js} +1 -1
  27. package/dist/gateway/static/root/assets/{skills-page-WO0bbJ54.js → skills-page-D_Az1SlU.js} +1 -1
  28. package/dist/gateway/static/root/assets/{theme-store-XxRFRZDX.js → theme-store-e2q2yjs4.js} +1 -1
  29. package/dist/gateway/static/root/assets/{url-6zpynn1R.js → url-DpFBIyN9.js} +2 -2
  30. package/dist/gateway/static/root/assets/{utils-uTYKh54l.js → utils-OA_b1q0Q.js} +1 -1
  31. package/dist/gateway/static/root/assets/{voice-api-key-field-BIAYHRs-.js → voice-api-key-field-SJml1hAt.js} +1 -1
  32. package/dist/gateway/static/root/assets/{workflow-page.utils-BbWhqD36.js → workflow-page.utils-D90VVCzC.js} +1 -1
  33. package/dist/gateway/static/root/assets/{workflows-page-D4RIF7E1.js → workflows-page-y7Btji0J.js} +1 -1
  34. package/dist/gateway/static/root/index.html +5 -5
  35. package/dist/package.js +1 -1
  36. package/dist/src/agent/agent-manager.js +1 -3
  37. package/dist/src/agent/agent-manager.js.map +1 -1
  38. package/dist/src/agent/bootstrap/bootstrap-cache.d.ts +0 -1
  39. package/dist/src/agent/bootstrap/bootstrap-cache.js +2 -3
  40. package/dist/src/agent/bootstrap/bootstrap-cache.js.map +1 -1
  41. package/dist/src/agent/bootstrap/bootstrap-files.d.ts +0 -5
  42. package/dist/src/agent/bootstrap/bootstrap-files.js +4 -5
  43. package/dist/src/agent/bootstrap/bootstrap-files.js.map +1 -1
  44. package/dist/src/agent/bootstrap/load-bootstrap-files.d.ts +2 -6
  45. package/dist/src/agent/bootstrap/load-bootstrap-files.js +6 -21
  46. package/dist/src/agent/bootstrap/load-bootstrap-files.js.map +1 -1
  47. package/dist/src/agent/bootstrap/types.d.ts +5 -5
  48. package/dist/src/agent/context/workspace-seed.js +5 -9
  49. package/dist/src/agent/context/workspace-seed.js.map +1 -1
  50. package/dist/src/agent/context/workspace-state.d.ts +3 -35
  51. package/dist/src/agent/context/workspace-state.js +9 -53
  52. package/dist/src/agent/context/workspace-state.js.map +1 -1
  53. package/dist/src/agent/context/workspace-templates/AGENTS.md +0 -4
  54. package/dist/src/agent/prompt/system-prompt.js +0 -1
  55. package/dist/src/agent/prompt/system-prompt.js.map +1 -1
  56. package/dist/src/agent/tools/tool-paths.js +1 -3
  57. package/dist/src/agent/tools/tool-paths.js.map +1 -1
  58. package/dist/src/chat-commands/agent-edit.js +1 -2
  59. package/dist/src/chat-commands/agent-edit.js.map +1 -1
  60. package/dist/src/cli/commands/onboard.js +0 -8
  61. package/dist/src/cli/commands/onboard.js.map +1 -1
  62. package/dist/src/cli/templates.d.ts +3 -10
  63. package/dist/src/cli/templates.js +4 -32
  64. package/dist/src/cli/templates.js.map +1 -1
  65. package/dist/src/config/paths.d.ts +0 -1
  66. package/dist/src/config/paths.js +1 -2
  67. package/dist/src/config/paths.js.map +1 -1
  68. package/dist/src/config/schema.js +1 -1
  69. package/dist/src/config/schema.js.map +1 -1
  70. package/dist/src/gateway/agents-admin.js +1 -6
  71. package/dist/src/gateway/agents-admin.js.map +1 -1
  72. package/dist/src/gateway/heartbeat/service.js +1 -1
  73. package/dist/src/heartbeat/index.js +1 -1
  74. package/package.json +1 -1
  75. package/dist/gateway/static/root/assets/index-BvEhL9RQ.css +0 -1
  76. package/dist/src/agent/context/workspace-templates/BOOTSTRAP.md +0 -61
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-files.js","names":[],"sources":["../../../../src/agent/bootstrap/bootstrap-files.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { DEFAULT_HEARTBEAT_FILENAME } from '../context/workspace.js';\nimport {\n getOrLoadBootstrapFiles,\n markBootstrapContextInjected,\n wasBootstrapContextInjected,\n} from './bootstrap-cache.js';\nimport {\n buildBootstrapContextFiles,\n resolveBootstrapMaxChars,\n resolveBootstrapTotalMaxChars,\n} from './bootstrap-context.js';\nimport { filterBootstrapFilesForSession } from './filter-bootstrap-files.js';\nimport { loadProfileBootstrapFiles } from './load-bootstrap-files.js';\nimport type { EmbeddedContextFile, WorkspaceBootstrapFile } from './types.js';\n\nexport { isProfileBootstrapPending } from './load-bootstrap-files.js';\nexport { clearAllBootstrapSnapshots, clearBootstrapSnapshot } from './bootstrap-cache.js';\n\nfunction filterHeartbeatBootstrapFile(\n files: WorkspaceBootstrapFile[],\n exclude: boolean,\n): WorkspaceBootstrapFile[] {\n if (!exclude) {\n return files;\n }\n return files.filter((file) => file.name !== DEFAULT_HEARTBEAT_FILENAME);\n}\n\nexport function resolveBootstrapFilesSync(params: {\n profileDir: string;\n workspaceStatePath: string;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n}): WorkspaceBootstrapFile[] {\n const rawFiles = loadProfileBootstrapFiles(params.profileDir, params.workspaceStatePath);\n const filtered = filterBootstrapFilesForSession(rawFiles, params.sessionKey);\n return filterHeartbeatBootstrapFile(filtered, params.excludeHeartbeat ?? false);\n}\n\nexport async function resolveBootstrapFilesForRun(params: {\n profileDir: string;\n workspaceStatePath: string;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n warn?: (message: string) => void;\n}): Promise<WorkspaceBootstrapFile[]> {\n const sessionKey = params.sessionKey;\n const rawFiles = sessionKey\n ? await getOrLoadBootstrapFiles({\n profileDir: params.profileDir,\n workspaceStatePath: params.workspaceStatePath,\n sessionKey,\n })\n : loadProfileBootstrapFiles(params.profileDir, params.workspaceStatePath);\n const filtered = filterBootstrapFilesForSession(rawFiles, sessionKey);\n return filterHeartbeatBootstrapFile(filtered, params.excludeHeartbeat ?? false);\n}\n\nexport function resolveBootstrapContextSync(params: {\n profileDir: string;\n workspaceStatePath: string;\n config?: Config;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n contextInjection?: 'always' | 'continuation-skip' | 'never';\n}): {\n bootstrapFiles: WorkspaceBootstrapFile[];\n contextFiles: EmbeddedContextFile[];\n} {\n const mode = params.contextInjection ?? 'always';\n if (mode === 'never') {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n if (\n mode === 'continuation-skip' &&\n params.sessionKey &&\n wasBootstrapContextInjected(params.sessionKey)\n ) {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n const bootstrapFiles = resolveBootstrapFilesSync(params);\n const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {\n maxChars: resolveBootstrapMaxChars(params.config),\n totalMaxChars: resolveBootstrapTotalMaxChars(params.config),\n });\n if (mode === 'continuation-skip' && params.sessionKey && contextFiles.length > 0) {\n markBootstrapContextInjected(params.sessionKey);\n }\n return { bootstrapFiles, contextFiles };\n}\n\nexport async function resolveBootstrapContextForRun(params: {\n profileDir: string;\n workspaceStatePath: string;\n config?: Config;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n contextInjection?: 'always' | 'continuation-skip' | 'never';\n warn?: (message: string) => void;\n}): Promise<{\n bootstrapFiles: WorkspaceBootstrapFile[];\n contextFiles: EmbeddedContextFile[];\n}> {\n const mode = params.contextInjection ?? 'always';\n if (mode === 'never') {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n if (\n mode === 'continuation-skip' &&\n params.sessionKey &&\n wasBootstrapContextInjected(params.sessionKey)\n ) {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n const bootstrapFiles = await resolveBootstrapFilesForRun(params);\n const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {\n maxChars: resolveBootstrapMaxChars(params.config),\n totalMaxChars: resolveBootstrapTotalMaxChars(params.config),\n warn: params.warn,\n });\n if (mode === 'continuation-skip' && params.sessionKey && contextFiles.length > 0) {\n markBootstrapContextInjected(params.sessionKey);\n }\n return { bootstrapFiles, contextFiles };\n}\n"],"mappings":";;;;;;AAmBA,SAAS,6BACP,OACA,SAC0B;AAC1B,KAAI,CAAC,QACH,QAAO;AAET,QAAO,MAAM,QAAQ,SAAS,KAAK,SAAS,2BAA2B;;AAGzE,SAAgB,0BAA0B,QAKb;AAG3B,QAAO,6BADU,+BADA,0BAA0B,OAAO,YAAY,OAAO,mBACb,EAAE,OAAO,WACrB,EAAE,OAAO,oBAAoB,MAAM;;AAGjF,eAAsB,4BAA4B,QAMZ;CACpC,MAAM,aAAa,OAAO;AAS1B,QAAO,6BADU,+BAPA,aACb,MAAM,wBAAwB;EAC5B,YAAY,OAAO;EACnB,oBAAoB,OAAO;EAC3B;EACD,CAAC,GACF,0BAA0B,OAAO,YAAY,OAAO,mBAAmB,EACjB,WACd,EAAE,OAAO,oBAAoB,MAAM;;AAGjF,SAAgB,4BAA4B,QAU1C;CACA,MAAM,OAAO,OAAO,oBAAoB;AACxC,KAAI,SAAS,QACX,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;AAEjD,KACE,SAAS,uBACT,OAAO,cACP,4BAA4B,OAAO,WAAW,CAE9C,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;CAEjD,MAAM,iBAAiB,0BAA0B,OAAO;CACxD,MAAM,eAAe,2BAA2B,gBAAgB;EAC9D,UAAU,yBAAyB,OAAO,OAAO;EACjD,eAAe,8BAA8B,OAAO,OAAO;EAC5D,CAAC;AACF,KAAI,SAAS,uBAAuB,OAAO,cAAc,aAAa,SAAS,EAC7E,8BAA6B,OAAO,WAAW;AAEjD,QAAO;EAAE;EAAgB;EAAc;;AAGzC,eAAsB,8BAA8B,QAWjD;CACD,MAAM,OAAO,OAAO,oBAAoB;AACxC,KAAI,SAAS,QACX,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;AAEjD,KACE,SAAS,uBACT,OAAO,cACP,4BAA4B,OAAO,WAAW,CAE9C,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;CAEjD,MAAM,iBAAiB,MAAM,4BAA4B,OAAO;CAChE,MAAM,eAAe,2BAA2B,gBAAgB;EAC9D,UAAU,yBAAyB,OAAO,OAAO;EACjD,eAAe,8BAA8B,OAAO,OAAO;EAC3D,MAAM,OAAO;EACd,CAAC;AACF,KAAI,SAAS,uBAAuB,OAAO,cAAc,aAAa,SAAS,EAC7E,8BAA6B,OAAO,WAAW;AAEjD,QAAO;EAAE;EAAgB;EAAc"}
1
+ {"version":3,"file":"bootstrap-files.js","names":[],"sources":["../../../../src/agent/bootstrap/bootstrap-files.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { DEFAULT_HEARTBEAT_FILENAME } from '../context/workspace.js';\nimport {\n getOrLoadBootstrapFiles,\n markBootstrapContextInjected,\n wasBootstrapContextInjected,\n} from './bootstrap-cache.js';\nimport {\n buildBootstrapContextFiles,\n resolveBootstrapMaxChars,\n resolveBootstrapTotalMaxChars,\n} from './bootstrap-context.js';\nimport { filterBootstrapFilesForSession } from './filter-bootstrap-files.js';\nimport { loadProfileBootstrapFiles } from './load-bootstrap-files.js';\nimport type { EmbeddedContextFile, WorkspaceBootstrapFile } from './types.js';\n\nexport { clearAllBootstrapSnapshots, clearBootstrapSnapshot } from './bootstrap-cache.js';\n\nfunction filterHeartbeatBootstrapFile(\n files: WorkspaceBootstrapFile[],\n exclude: boolean,\n): WorkspaceBootstrapFile[] {\n if (!exclude) {\n return files;\n }\n return files.filter((file) => file.name !== DEFAULT_HEARTBEAT_FILENAME);\n}\n\nexport function resolveBootstrapFilesSync(params: {\n profileDir: string;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n}): WorkspaceBootstrapFile[] {\n const rawFiles = loadProfileBootstrapFiles(params.profileDir);\n const filtered = filterBootstrapFilesForSession(rawFiles, params.sessionKey);\n return filterHeartbeatBootstrapFile(filtered, params.excludeHeartbeat ?? false);\n}\n\nexport async function resolveBootstrapFilesForRun(params: {\n profileDir: string;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n warn?: (message: string) => void;\n}): Promise<WorkspaceBootstrapFile[]> {\n const sessionKey = params.sessionKey;\n const rawFiles = sessionKey\n ? await getOrLoadBootstrapFiles({\n profileDir: params.profileDir,\n sessionKey,\n })\n : loadProfileBootstrapFiles(params.profileDir);\n const filtered = filterBootstrapFilesForSession(rawFiles, sessionKey);\n return filterHeartbeatBootstrapFile(filtered, params.excludeHeartbeat ?? false);\n}\n\nexport function resolveBootstrapContextSync(params: {\n profileDir: string;\n config?: Config;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n contextInjection?: 'always' | 'continuation-skip' | 'never';\n}): {\n bootstrapFiles: WorkspaceBootstrapFile[];\n contextFiles: EmbeddedContextFile[];\n} {\n const mode = params.contextInjection ?? 'always';\n if (mode === 'never') {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n if (\n mode === 'continuation-skip' &&\n params.sessionKey &&\n wasBootstrapContextInjected(params.sessionKey)\n ) {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n const bootstrapFiles = resolveBootstrapFilesSync(params);\n const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {\n maxChars: resolveBootstrapMaxChars(params.config),\n totalMaxChars: resolveBootstrapTotalMaxChars(params.config),\n });\n if (mode === 'continuation-skip' && params.sessionKey && contextFiles.length > 0) {\n markBootstrapContextInjected(params.sessionKey);\n }\n return { bootstrapFiles, contextFiles };\n}\n\nexport async function resolveBootstrapContextForRun(params: {\n profileDir: string;\n config?: Config;\n sessionKey?: string;\n excludeHeartbeat?: boolean;\n contextInjection?: 'always' | 'continuation-skip' | 'never';\n warn?: (message: string) => void;\n}): Promise<{\n bootstrapFiles: WorkspaceBootstrapFile[];\n contextFiles: EmbeddedContextFile[];\n}> {\n const mode = params.contextInjection ?? 'always';\n if (mode === 'never') {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n if (\n mode === 'continuation-skip' &&\n params.sessionKey &&\n wasBootstrapContextInjected(params.sessionKey)\n ) {\n return { bootstrapFiles: [], contextFiles: [] };\n }\n const bootstrapFiles = await resolveBootstrapFilesForRun(params);\n const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {\n maxChars: resolveBootstrapMaxChars(params.config),\n totalMaxChars: resolveBootstrapTotalMaxChars(params.config),\n warn: params.warn,\n });\n if (mode === 'continuation-skip' && params.sessionKey && contextFiles.length > 0) {\n markBootstrapContextInjected(params.sessionKey);\n }\n return { bootstrapFiles, contextFiles };\n}\n"],"mappings":";;;;;;AAkBA,SAAS,6BACP,OACA,SAC0B;AAC1B,KAAI,CAAC,QACH,QAAO;AAET,QAAO,MAAM,QAAQ,SAAS,KAAK,SAAS,2BAA2B;;AAGzE,SAAgB,0BAA0B,QAIb;AAG3B,QAAO,6BADU,+BADA,0BAA0B,OAAO,WACM,EAAE,OAAO,WACrB,EAAE,OAAO,oBAAoB,MAAM;;AAGjF,eAAsB,4BAA4B,QAKZ;CACpC,MAAM,aAAa,OAAO;AAQ1B,QAAO,6BADU,+BANA,aACb,MAAM,wBAAwB;EAC5B,YAAY,OAAO;EACnB;EACD,CAAC,GACF,0BAA0B,OAAO,WAAW,EACU,WACd,EAAE,OAAO,oBAAoB,MAAM;;AAGjF,SAAgB,4BAA4B,QAS1C;CACA,MAAM,OAAO,OAAO,oBAAoB;AACxC,KAAI,SAAS,QACX,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;AAEjD,KACE,SAAS,uBACT,OAAO,cACP,4BAA4B,OAAO,WAAW,CAE9C,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;CAEjD,MAAM,iBAAiB,0BAA0B,OAAO;CACxD,MAAM,eAAe,2BAA2B,gBAAgB;EAC9D,UAAU,yBAAyB,OAAO,OAAO;EACjD,eAAe,8BAA8B,OAAO,OAAO;EAC5D,CAAC;AACF,KAAI,SAAS,uBAAuB,OAAO,cAAc,aAAa,SAAS,EAC7E,8BAA6B,OAAO,WAAW;AAEjD,QAAO;EAAE;EAAgB;EAAc;;AAGzC,eAAsB,8BAA8B,QAUjD;CACD,MAAM,OAAO,OAAO,oBAAoB;AACxC,KAAI,SAAS,QACX,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;AAEjD,KACE,SAAS,uBACT,OAAO,cACP,4BAA4B,OAAO,WAAW,CAE9C,QAAO;EAAE,gBAAgB,EAAE;EAAE,cAAc,EAAE;EAAE;CAEjD,MAAM,iBAAiB,MAAM,4BAA4B,OAAO;CAChE,MAAM,eAAe,2BAA2B,gBAAgB;EAC9D,UAAU,yBAAyB,OAAO,OAAO;EACjD,eAAe,8BAA8B,OAAO,OAAO;EAC3D,MAAM,OAAO;EACd,CAAC;AACF,KAAI,SAAS,uBAAuB,OAAO,cAAc,aAAa,SAAS,EAC7E,8BAA6B,OAAO,WAAW;AAEjD,QAAO;EAAE;EAAgB;EAAc"}
@@ -1,10 +1,6 @@
1
1
  import type { WorkspaceBootstrapFile } from './types.js';
2
- export declare function isProfileBootstrapPending(profileDir: string, workspaceStatePath: string): boolean;
3
2
  /**
4
3
  * Load bootstrap profile Markdown from `agents/<id>/profile/`.
5
- * MEMORY.md and BOOTSTRAP.md are omitted when absent; other slots emit missing markers.
6
- *
7
- * BOOTSTRAP.md is also filtered out when `setupCompletedAt` is set in the workspace state,
8
- * to prevent re-injection of the bootstrap script after setup is done.
4
+ * MEMORY.md is omitted when absent; other slots emit missing markers.
9
5
  */
10
- export declare function loadProfileBootstrapFiles(profileDir: string, workspaceStatePath: string): WorkspaceBootstrapFile[];
6
+ export declare function loadProfileBootstrapFiles(profileDir: string): WorkspaceBootstrapFile[];
@@ -1,18 +1,14 @@
1
- import { WORKSPACE_FILES, init_paths } from "../../config/paths.js";
2
1
  import { DEFAULT_AGENTS_FILENAME, DEFAULT_HEARTBEAT_FILENAME, DEFAULT_IDENTITY_FILENAME, DEFAULT_MEMORY_FILENAME, DEFAULT_SOUL_FILENAME, DEFAULT_TOOLS_FILENAME, DEFAULT_USER_FILENAME, stripFrontMatter } from "../context/workspace.js";
3
- import { isWorkspaceSetupCompleted, syncBootstrapSetupCompletion } from "../context/workspace-state.js";
4
2
  import { existsSync, readFileSync } from "node:fs";
5
3
  import { join, resolve } from "node:path";
6
4
  //#region src/agent/bootstrap/load-bootstrap-files.ts
7
- init_paths();
8
- const BOOTSTRAP_LOAD_ORDER = [
5
+ const PROFILE_LOAD_ORDER = [
9
6
  DEFAULT_AGENTS_FILENAME,
10
7
  DEFAULT_SOUL_FILENAME,
11
8
  DEFAULT_TOOLS_FILENAME,
12
9
  DEFAULT_IDENTITY_FILENAME,
13
10
  DEFAULT_USER_FILENAME,
14
11
  DEFAULT_HEARTBEAT_FILENAME,
15
- WORKSPACE_FILES.BOOTSTRAP,
16
12
  DEFAULT_MEMORY_FILENAME
17
13
  ];
18
14
  function readProfileFile(filePath) {
@@ -23,27 +19,16 @@ function readProfileFile(filePath) {
23
19
  return null;
24
20
  }
25
21
  }
26
- function isProfileBootstrapPending(profileDir, workspaceStatePath) {
27
- syncBootstrapSetupCompletion(workspaceStatePath, profileDir);
28
- if (isWorkspaceSetupCompleted(workspaceStatePath)) return false;
29
- return existsSync(join(profileDir, WORKSPACE_FILES.BOOTSTRAP));
30
- }
31
22
  /**
32
23
  * Load bootstrap profile Markdown from `agents/<id>/profile/`.
33
- * MEMORY.md and BOOTSTRAP.md are omitted when absent; other slots emit missing markers.
34
- *
35
- * BOOTSTRAP.md is also filtered out when `setupCompletedAt` is set in the workspace state,
36
- * to prevent re-injection of the bootstrap script after setup is done.
24
+ * MEMORY.md is omitted when absent; other slots emit missing markers.
37
25
  */
38
- function loadProfileBootstrapFiles(profileDir, workspaceStatePath) {
39
- syncBootstrapSetupCompletion(workspaceStatePath, profileDir);
26
+ function loadProfileBootstrapFiles(profileDir) {
40
27
  const resolvedDir = resolve(profileDir);
41
28
  const result = [];
42
- const setupCompleted = isWorkspaceSetupCompleted(workspaceStatePath);
43
- for (const name of BOOTSTRAP_LOAD_ORDER) {
29
+ for (const name of PROFILE_LOAD_ORDER) {
44
30
  const filePath = join(resolvedDir, name);
45
- if (name === WORKSPACE_FILES.BOOTSTRAP && setupCompleted) continue;
46
- if (name === "MEMORY.md" || name === WORKSPACE_FILES.BOOTSTRAP) {
31
+ if (name === "MEMORY.md") {
47
32
  const content = readProfileFile(filePath);
48
33
  if (content === null) continue;
49
34
  result.push({
@@ -70,6 +55,6 @@ function loadProfileBootstrapFiles(profileDir, workspaceStatePath) {
70
55
  return result;
71
56
  }
72
57
  //#endregion
73
- export { isProfileBootstrapPending, loadProfileBootstrapFiles };
58
+ export { loadProfileBootstrapFiles };
74
59
 
75
60
  //# sourceMappingURL=load-bootstrap-files.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-bootstrap-files.js","names":[],"sources":["../../../../src/agent/bootstrap/load-bootstrap-files.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { WORKSPACE_FILES } from '../../config/paths.js';\nimport { stripFrontMatter } from '../context/workspace.js';\nimport {\n isWorkspaceSetupCompleted,\n syncBootstrapSetupCompletion,\n} from '../context/workspace-state.js';\nimport {\n DEFAULT_AGENTS_FILENAME,\n DEFAULT_HEARTBEAT_FILENAME,\n DEFAULT_IDENTITY_FILENAME,\n DEFAULT_MEMORY_FILENAME,\n DEFAULT_SOUL_FILENAME,\n DEFAULT_TOOLS_FILENAME,\n DEFAULT_USER_FILENAME,\n} from '../context/workspace.js';\nimport type { BootstrapFileName, WorkspaceBootstrapFile } from './types.js';\n\nconst BOOTSTRAP_LOAD_ORDER: BootstrapFileName[] = [\n DEFAULT_AGENTS_FILENAME,\n DEFAULT_SOUL_FILENAME,\n DEFAULT_TOOLS_FILENAME,\n DEFAULT_IDENTITY_FILENAME,\n DEFAULT_USER_FILENAME,\n DEFAULT_HEARTBEAT_FILENAME,\n WORKSPACE_FILES.BOOTSTRAP,\n DEFAULT_MEMORY_FILENAME,\n];\n\nfunction readProfileFile(filePath: string): string | null {\n if (!existsSync(filePath)) {\n return null;\n }\n try {\n return stripFrontMatter(readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nexport function isProfileBootstrapPending(profileDir: string, workspaceStatePath: string): boolean {\n syncBootstrapSetupCompletion(workspaceStatePath, profileDir);\n if (isWorkspaceSetupCompleted(workspaceStatePath)) {\n return false;\n }\n return existsSync(join(profileDir, WORKSPACE_FILES.BOOTSTRAP));\n}\n\n/**\n * Load bootstrap profile Markdown from `agents/<id>/profile/`.\n * MEMORY.md and BOOTSTRAP.md are omitted when absent; other slots emit missing markers.\n *\n * BOOTSTRAP.md is also filtered out when `setupCompletedAt` is set in the workspace state,\n * to prevent re-injection of the bootstrap script after setup is done.\n */\nexport function loadProfileBootstrapFiles(\n profileDir: string,\n workspaceStatePath: string,\n): WorkspaceBootstrapFile[] {\n syncBootstrapSetupCompletion(workspaceStatePath, profileDir);\n const resolvedDir = resolve(profileDir);\n const result: WorkspaceBootstrapFile[] = [];\n const setupCompleted = isWorkspaceSetupCompleted(workspaceStatePath);\n\n for (const name of BOOTSTRAP_LOAD_ORDER) {\n const filePath = join(resolvedDir, name);\n\n // Skip BOOTSTRAP.md entirely when setup is already completed\n if (name === WORKSPACE_FILES.BOOTSTRAP && setupCompleted) {\n continue;\n }\n\n if (name === DEFAULT_MEMORY_FILENAME || name === WORKSPACE_FILES.BOOTSTRAP) {\n const content = readProfileFile(filePath);\n if (content === null) {\n continue;\n }\n result.push({ name, path: filePath, content, missing: false });\n continue;\n }\n\n const content = readProfileFile(filePath);\n if (content !== null) {\n result.push({ name, path: filePath, content, missing: false });\n } else {\n result.push({ name, path: filePath, missing: true });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;;YAGwD;AAiBxD,MAAM,uBAA4C;CAChD;CACA;CACA;CACA;CACA;CACA;CACA,gBAAgB;CAChB;CACD;AAED,SAAS,gBAAgB,UAAiC;AACxD,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAET,KAAI;AACF,SAAO,iBAAiB,aAAa,UAAU,QAAQ,CAAC;SAClD;AACN,SAAO;;;AAIX,SAAgB,0BAA0B,YAAoB,oBAAqC;AACjG,8BAA6B,oBAAoB,WAAW;AAC5D,KAAI,0BAA0B,mBAAmB,CAC/C,QAAO;AAET,QAAO,WAAW,KAAK,YAAY,gBAAgB,UAAU,CAAC;;;;;;;;;AAUhE,SAAgB,0BACd,YACA,oBAC0B;AAC1B,8BAA6B,oBAAoB,WAAW;CAC5D,MAAM,cAAc,QAAQ,WAAW;CACvC,MAAM,SAAmC,EAAE;CAC3C,MAAM,iBAAiB,0BAA0B,mBAAmB;AAEpE,MAAK,MAAM,QAAQ,sBAAsB;EACvC,MAAM,WAAW,KAAK,aAAa,KAAK;AAGxC,MAAI,SAAS,gBAAgB,aAAa,eACxC;AAGF,MAAI,SAAA,eAAoC,SAAS,gBAAgB,WAAW;GAC1E,MAAM,UAAU,gBAAgB,SAAS;AACzC,OAAI,YAAY,KACd;AAEF,UAAO,KAAK;IAAE;IAAM,MAAM;IAAU;IAAS,SAAS;IAAO,CAAC;AAC9D;;EAGF,MAAM,UAAU,gBAAgB,SAAS;AACzC,MAAI,YAAY,KACd,QAAO,KAAK;GAAE;GAAM,MAAM;GAAU;GAAS,SAAS;GAAO,CAAC;MAE9D,QAAO,KAAK;GAAE;GAAM,MAAM;GAAU,SAAS;GAAM,CAAC;;AAIxD,QAAO"}
1
+ {"version":3,"file":"load-bootstrap-files.js","names":[],"sources":["../../../../src/agent/bootstrap/load-bootstrap-files.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { stripFrontMatter } from '../context/workspace.js';\nimport {\n DEFAULT_AGENTS_FILENAME,\n DEFAULT_HEARTBEAT_FILENAME,\n DEFAULT_IDENTITY_FILENAME,\n DEFAULT_MEMORY_FILENAME,\n DEFAULT_SOUL_FILENAME,\n DEFAULT_TOOLS_FILENAME,\n DEFAULT_USER_FILENAME,\n} from '../context/workspace.js';\nimport type { BootstrapFileName, WorkspaceBootstrapFile } from './types.js';\n\nconst PROFILE_LOAD_ORDER: BootstrapFileName[] = [\n DEFAULT_AGENTS_FILENAME,\n DEFAULT_SOUL_FILENAME,\n DEFAULT_TOOLS_FILENAME,\n DEFAULT_IDENTITY_FILENAME,\n DEFAULT_USER_FILENAME,\n DEFAULT_HEARTBEAT_FILENAME,\n DEFAULT_MEMORY_FILENAME,\n];\n\nfunction readProfileFile(filePath: string): string | null {\n if (!existsSync(filePath)) {\n return null;\n }\n try {\n return stripFrontMatter(readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Load bootstrap profile Markdown from `agents/<id>/profile/`.\n * MEMORY.md is omitted when absent; other slots emit missing markers.\n */\nexport function loadProfileBootstrapFiles(profileDir: string): WorkspaceBootstrapFile[] {\n const resolvedDir = resolve(profileDir);\n const result: WorkspaceBootstrapFile[] = [];\n\n for (const name of PROFILE_LOAD_ORDER) {\n const filePath = join(resolvedDir, name);\n\n if (name === DEFAULT_MEMORY_FILENAME) {\n const content = readProfileFile(filePath);\n if (content === null) {\n continue;\n }\n result.push({ name, path: filePath, content, missing: false });\n continue;\n }\n\n const content = readProfileFile(filePath);\n if (content !== null) {\n result.push({ name, path: filePath, content, missing: false });\n } else {\n result.push({ name, path: filePath, missing: true });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;AAeA,MAAM,qBAA0C;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,gBAAgB,UAAiC;AACxD,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAET,KAAI;AACF,SAAO,iBAAiB,aAAa,UAAU,QAAQ,CAAC;SAClD;AACN,SAAO;;;;;;;AAQX,SAAgB,0BAA0B,YAA8C;CACtF,MAAM,cAAc,QAAQ,WAAW;CACvC,MAAM,SAAmC,EAAE;AAE3C,MAAK,MAAM,QAAQ,oBAAoB;EACrC,MAAM,WAAW,KAAK,aAAa,KAAK;AAExC,MAAI,SAAA,aAAkC;GACpC,MAAM,UAAU,gBAAgB,SAAS;AACzC,OAAI,YAAY,KACd;AAEF,UAAO,KAAK;IAAE;IAAM,MAAM;IAAU;IAAS,SAAS;IAAO,CAAC;AAC9D;;EAGF,MAAM,UAAU,gBAAgB,SAAS;AACzC,MAAI,YAAY,KACd,QAAO,KAAK;GAAE;GAAM,MAAM;GAAU;GAAS,SAAS;GAAO,CAAC;MAE9D,QAAO,KAAK;GAAE;GAAM,MAAM;GAAU,SAAS;GAAM,CAAC;;AAIxD,QAAO"}
@@ -1,12 +1,12 @@
1
1
  import type { AgentProfileMarkdownFilename } from '../context/workspace.js';
2
- export type BootstrapFileName = AgentProfileMarkdownFilename | 'BOOTSTRAP.md';
3
- export interface WorkspaceBootstrapFile {
2
+ export type BootstrapFileName = AgentProfileMarkdownFilename;
3
+ export type WorkspaceBootstrapFile = {
4
4
  name: BootstrapFileName;
5
5
  path: string;
6
6
  content?: string;
7
7
  missing: boolean;
8
- }
9
- export interface EmbeddedContextFile {
8
+ };
9
+ export type EmbeddedContextFile = {
10
10
  path: string;
11
11
  content: string;
12
- }
12
+ };
@@ -1,9 +1,8 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
- import { DEFAULT_AGENT_ID, init_agent_scope, resolveAgentProfileDir, resolveAgentWorkspaceDir } from "../agent-scope.js";
3
+ import { init_agent_scope, resolveAgentProfileDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agent-scope.js";
4
4
  import { WORKSPACE_FILES, init_paths } from "../../config/paths.js";
5
5
  import { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from "./workspace.js";
6
- import { markBootstrapSeeded, resolveWorkspaceStatePathForMarkdownWorkspace } from "./workspace-state.js";
7
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
7
  import { dirname, join } from "node:path";
9
8
  import { fileURLToPath } from "node:url";
@@ -19,8 +18,7 @@ init_logger();
19
18
  const log = createLogger("WorkspaceSeed");
20
19
  /** Marker in bundled/reference `IDENTITY.md` templates; replaced on agent creation when a display name is known. */
21
20
  const IDENTITY_NAME_PLACEHOLDER = "_(pick something you like)_";
22
- /** Files to copy when seeding a new agent (includes `BOOTSTRAP.md`, not part of system-prompt load order). */
23
- const SEED_FILENAMES = [...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, WORKSPACE_FILES.BOOTSTRAP];
21
+ const SEED_FILENAMES = [...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES];
24
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
23
  function resolveBundledTemplatesDir() {
26
24
  return join(__dirname, "workspace-templates");
@@ -81,10 +79,7 @@ function seedAgentProfileMarkdownFiles(profileDir, markdownWorkspaceDir, options
81
79
  log.warn({ name }, "Missing workspace template file; skip seeding");
82
80
  continue;
83
81
  }
84
- if (writeFileIfMissing(targetPath, name === WORKSPACE_FILES.IDENTITY ? personalizeIdentityTemplate(tpl, options?.displayName) : tpl)) {
85
- seeded++;
86
- if (name === WORKSPACE_FILES.BOOTSTRAP) markBootstrapSeeded(resolveWorkspaceStatePathForMarkdownWorkspace(markdownWorkspaceDir));
87
- }
82
+ if (writeFileIfMissing(targetPath, name === WORKSPACE_FILES.IDENTITY ? personalizeIdentityTemplate(tpl, options?.displayName) : tpl)) seeded++;
88
83
  }
89
84
  if (seeded > 0) log.info({
90
85
  profileDir,
@@ -111,7 +106,8 @@ function ensureGitRepo(markdownWorkspaceDir, isBrandNew) {
111
106
  * Ensure default (`main`) agent has reference profile Markdown templates (missing files only).
112
107
  */
113
108
  function seedMainAgentProfileMarkdown(cfg) {
114
- seedAgentProfileMarkdownFiles(resolveAgentProfileDir(cfg, DEFAULT_AGENT_ID), resolveAgentWorkspaceDir(cfg, DEFAULT_AGENT_ID));
109
+ const agentId = resolveDefaultAgentId(cfg);
110
+ seedAgentProfileMarkdownFiles(resolveAgentProfileDir(cfg, agentId), resolveAgentWorkspaceDir(cfg, agentId), { displayName: agentId });
115
111
  }
116
112
  //#endregion
117
113
  export { IDENTITY_NAME_PLACEHOLDER, seedAgentProfileMarkdownFiles, seedMainAgentProfileMarkdown };
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-seed.js","names":[],"sources":["../../../../src/agent/context/workspace-seed.ts"],"sourcesContent":["/**\n * Seed profile Markdown files into `agents/<agentId>/profile/` (and optionally `git init` the Markdown workspace).\n * Resolution order per file: `XOPC_TEMPLATE_PATH` or repo `docs/reference/templates`, then bundled `./workspace-templates/`.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Config } from '../../config/schema.js';\nimport { DEFAULT_AGENT_ID, resolveAgentProfileDir, resolveAgentWorkspaceDir } from '../agent-scope.js';\nimport { WORKSPACE_FILES } from '../../config/paths.js';\nimport { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from './workspace.js';\nimport { createLogger } from '../../utils/logger.js';\nimport {\n markBootstrapSeeded,\n resolveWorkspaceStatePathForMarkdownWorkspace,\n} from './workspace-state.js';\n\nconst log = createLogger('WorkspaceSeed');\n\n/** Marker in bundled/reference `IDENTITY.md` templates; replaced on agent creation when a display name is known. */\nexport const IDENTITY_NAME_PLACEHOLDER = '_(pick something you like)_';\n\nexport type SeedWorkspaceProfileMarkdownOptions = {\n /** Fills the **Name** line in `IDENTITY.md` when the template still contains the placeholder. */\n displayName?: string;\n};\n\n/** Files to copy when seeding a new agent (includes `BOOTSTRAP.md`, not part of system-prompt load order). */\nconst SEED_FILENAMES: readonly string[] = [...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, WORKSPACE_FILES.BOOTSTRAP];\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction resolveBundledTemplatesDir(): string {\n return join(__dirname, 'workspace-templates');\n}\n\n/** Walk ancestors for `docs/reference/templates` (dev checkout or local install with docs). */\nfunction resolveDocsTemplatesDirFromWalk(): string | null {\n let dir = __dirname;\n for (let i = 0; i < 12; i++) {\n const candidate = join(dir, 'docs', 'reference', 'templates');\n if (existsSync(candidate)) {\n return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/** Same convention as CLI `templates.ts`: env override, then docs tree, else null. */\nfunction resolvePrimaryTemplatesBaseDir(): string | null {\n const envPath = process.env.XOPC_TEMPLATE_PATH?.trim();\n if (envPath && existsSync(envPath)) {\n return envPath;\n }\n return resolveDocsTemplatesDirFromWalk();\n}\n\nfunction readTemplate(name: string): string | null {\n const primary = resolvePrimaryTemplatesBaseDir();\n if (primary) {\n const p = join(primary, name);\n if (existsSync(p)) {\n return readFileSync(p, 'utf-8');\n }\n }\n const bundled = join(resolveBundledTemplatesDir(), name);\n if (existsSync(bundled)) {\n return readFileSync(bundled, 'utf-8');\n }\n return null;\n}\n\nfunction writeFileIfMissing(targetPath: string, content: string): boolean {\n if (existsSync(targetPath)) {\n return false;\n }\n writeFileSync(targetPath, content, 'utf-8');\n return true;\n}\n\nfunction personalizeIdentityTemplate(content: string, displayName?: string): string {\n const n = displayName?.trim();\n if (!n || !content.includes(IDENTITY_NAME_PLACEHOLDER)) {\n return content;\n }\n return content.replaceAll(IDENTITY_NAME_PLACEHOLDER, n);\n}\n\n/**\n * Seed profile Markdown into `profileDir` (`agents/<id>/profile/`).\n * When `markdownWorkspaceDir` is set, runs `git init` on a brand-new Markdown workspace only (never under `profile/`).\n * Does not overwrite existing files.\n */\nexport function seedAgentProfileMarkdownFiles(\n profileDir: string,\n markdownWorkspaceDir: string,\n options?: SeedWorkspaceProfileMarkdownOptions,\n): void {\n const wsPreExisted = existsSync(markdownWorkspaceDir);\n mkdirSync(profileDir, { recursive: true });\n mkdirSync(markdownWorkspaceDir, { recursive: true });\n\n const isBrandNewWorkspace = !wsPreExisted;\n\n let seeded = 0;\n for (const name of SEED_FILENAMES) {\n const targetPath = join(profileDir, name);\n const tpl = readTemplate(name);\n if (!tpl) {\n log.warn({ name }, 'Missing workspace template file; skip seeding');\n continue;\n }\n const body =\n name === WORKSPACE_FILES.IDENTITY ? personalizeIdentityTemplate(tpl, options?.displayName) : tpl;\n if (writeFileIfMissing(targetPath, body)) {\n seeded++;\n // Track bootstrap seeding in workspace state\n if (name === WORKSPACE_FILES.BOOTSTRAP) {\n markBootstrapSeeded(resolveWorkspaceStatePathForMarkdownWorkspace(markdownWorkspaceDir));\n }\n }\n }\n\n if (seeded > 0) {\n log.info({ profileDir, seeded }, 'Seeded profile Markdown files');\n }\n\n ensureGitRepo(markdownWorkspaceDir, isBrandNewWorkspace);\n}\n\n/** Attempt `git init` on a brand-new Markdown workspace; silently skip on failure. */\nfunction ensureGitRepo(markdownWorkspaceDir: string, isBrandNew: boolean): void {\n if (!isBrandNew) {\n return;\n }\n if (existsSync(join(markdownWorkspaceDir, '.git'))) {\n return;\n }\n try {\n execFileSync('git', ['init'], { cwd: markdownWorkspaceDir, stdio: 'ignore', timeout: 5_000 });\n log.info({ markdownWorkspaceDir }, 'Initialized git repo in Markdown workspace');\n } catch {\n log.debug({ markdownWorkspaceDir }, 'git init skipped (git not available or failed)');\n }\n}\n\n/**\n * Ensure default (`main`) agent has reference profile Markdown templates (missing files only).\n */\nexport function seedMainAgentProfileMarkdown(cfg: Config): void {\n seedAgentProfileMarkdownFiles(\n resolveAgentProfileDir(cfg, DEFAULT_AGENT_ID),\n resolveAgentWorkspaceDir(cfg, DEFAULT_AGENT_ID),\n );\n}"],"mappings":";;;;;;;;;;;;;;;kBAWuG;YAC/C;aAEH;AAMrD,MAAM,MAAM,aAAa,gBAAgB;;AAGzC,MAAa,4BAA4B;;AAQzC,MAAM,iBAAoC,CAAC,GAAG,qCAAqC,gBAAgB,UAAU;AAE7G,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,6BAAqC;AAC5C,QAAO,KAAK,WAAW,sBAAsB;;;AAI/C,SAAS,kCAAiD;CACxD,IAAI,MAAM;AACV,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,YAAY,KAAK,KAAK,QAAQ,aAAa,YAAY;AAC7D,MAAI,WAAW,UAAU,CACvB,QAAO;EAET,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,QAAO;;;AAIT,SAAS,iCAAgD;CACvD,MAAM,UAAU,QAAQ,IAAI,oBAAoB,MAAM;AACtD,KAAI,WAAW,WAAW,QAAQ,CAChC,QAAO;AAET,QAAO,iCAAiC;;AAG1C,SAAS,aAAa,MAA6B;CACjD,MAAM,UAAU,gCAAgC;AAChD,KAAI,SAAS;EACX,MAAM,IAAI,KAAK,SAAS,KAAK;AAC7B,MAAI,WAAW,EAAE,CACf,QAAO,aAAa,GAAG,QAAQ;;CAGnC,MAAM,UAAU,KAAK,4BAA4B,EAAE,KAAK;AACxD,KAAI,WAAW,QAAQ,CACrB,QAAO,aAAa,SAAS,QAAQ;AAEvC,QAAO;;AAGT,SAAS,mBAAmB,YAAoB,SAA0B;AACxE,KAAI,WAAW,WAAW,CACxB,QAAO;AAET,eAAc,YAAY,SAAS,QAAQ;AAC3C,QAAO;;AAGT,SAAS,4BAA4B,SAAiB,aAA8B;CAClF,MAAM,IAAI,aAAa,MAAM;AAC7B,KAAI,CAAC,KAAK,CAAC,QAAQ,SAAA,8BAAmC,CACpD,QAAO;AAET,QAAO,QAAQ,WAAW,2BAA2B,EAAE;;;;;;;AAQzD,SAAgB,8BACd,YACA,sBACA,SACM;CACN,MAAM,eAAe,WAAW,qBAAqB;AACrD,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAC1C,WAAU,sBAAsB,EAAE,WAAW,MAAM,CAAC;CAEpD,MAAM,sBAAsB,CAAC;CAE7B,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,aAAa,KAAK,YAAY,KAAK;EACzC,MAAM,MAAM,aAAa,KAAK;AAC9B,MAAI,CAAC,KAAK;AACR,OAAI,KAAK,EAAE,MAAM,EAAE,gDAAgD;AACnE;;AAIF,MAAI,mBAAmB,YADrB,SAAS,gBAAgB,WAAW,4BAA4B,KAAK,SAAS,YAAY,GAAG,IACvD,EAAE;AACxC;AAEA,OAAI,SAAS,gBAAgB,UAC3B,qBAAoB,8CAA8C,qBAAqB,CAAC;;;AAK9F,KAAI,SAAS,EACX,KAAI,KAAK;EAAE;EAAY;EAAQ,EAAE,gCAAgC;AAGnE,eAAc,sBAAsB,oBAAoB;;;AAI1D,SAAS,cAAc,sBAA8B,YAA2B;AAC9E,KAAI,CAAC,WACH;AAEF,KAAI,WAAW,KAAK,sBAAsB,OAAO,CAAC,CAChD;AAEF,KAAI;AACF,eAAa,OAAO,CAAC,OAAO,EAAE;GAAE,KAAK;GAAsB,OAAO;GAAU,SAAS;GAAO,CAAC;AAC7F,MAAI,KAAK,EAAE,sBAAsB,EAAE,6CAA6C;SAC1E;AACN,MAAI,MAAM,EAAE,sBAAsB,EAAE,iDAAiD;;;;;;AAOzF,SAAgB,6BAA6B,KAAmB;AAC9D,+BACE,uBAAuB,KAAK,iBAAiB,EAC7C,yBAAyB,KAAK,iBAAiB,CAChD"}
1
+ {"version":3,"file":"workspace-seed.js","names":[],"sources":["../../../../src/agent/context/workspace-seed.ts"],"sourcesContent":["/**\n * Seed profile Markdown files into `agents/<agentId>/profile/` (and optionally `git init` the Markdown workspace).\n * Resolution order per file: `XOPC_TEMPLATE_PATH` or repo `docs/reference/templates`, then bundled `./workspace-templates/`.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Config } from '../../config/schema.js';\nimport {\n resolveAgentProfileDir,\n resolveAgentWorkspaceDir,\n resolveDefaultAgentId,\n} from '../agent-scope.js';\nimport { WORKSPACE_FILES } from '../../config/paths.js';\nimport { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from './workspace.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('WorkspaceSeed');\n\n/** Marker in bundled/reference `IDENTITY.md` templates; replaced on agent creation when a display name is known. */\nexport const IDENTITY_NAME_PLACEHOLDER = '_(pick something you like)_';\n\nexport type SeedWorkspaceProfileMarkdownOptions = {\n /** Fills the **Name** line in `IDENTITY.md` when the template still contains the placeholder. */\n displayName?: string;\n};\n\nconst SEED_FILENAMES: readonly string[] = [...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES];\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction resolveBundledTemplatesDir(): string {\n return join(__dirname, 'workspace-templates');\n}\n\n/** Walk ancestors for `docs/reference/templates` (dev checkout or local install with docs). */\nfunction resolveDocsTemplatesDirFromWalk(): string | null {\n let dir = __dirname;\n for (let i = 0; i < 12; i++) {\n const candidate = join(dir, 'docs', 'reference', 'templates');\n if (existsSync(candidate)) {\n return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/** Same convention as CLI `templates.ts`: env override, then docs tree, else null. */\nfunction resolvePrimaryTemplatesBaseDir(): string | null {\n const envPath = process.env.XOPC_TEMPLATE_PATH?.trim();\n if (envPath && existsSync(envPath)) {\n return envPath;\n }\n return resolveDocsTemplatesDirFromWalk();\n}\n\nfunction readTemplate(name: string): string | null {\n const primary = resolvePrimaryTemplatesBaseDir();\n if (primary) {\n const p = join(primary, name);\n if (existsSync(p)) {\n return readFileSync(p, 'utf-8');\n }\n }\n const bundled = join(resolveBundledTemplatesDir(), name);\n if (existsSync(bundled)) {\n return readFileSync(bundled, 'utf-8');\n }\n return null;\n}\n\nfunction writeFileIfMissing(targetPath: string, content: string): boolean {\n if (existsSync(targetPath)) {\n return false;\n }\n writeFileSync(targetPath, content, 'utf-8');\n return true;\n}\n\nfunction personalizeIdentityTemplate(content: string, displayName?: string): string {\n const n = displayName?.trim();\n if (!n || !content.includes(IDENTITY_NAME_PLACEHOLDER)) {\n return content;\n }\n return content.replaceAll(IDENTITY_NAME_PLACEHOLDER, n);\n}\n\n/**\n * Seed profile Markdown into `profileDir` (`agents/<id>/profile/`).\n * When `markdownWorkspaceDir` is set, runs `git init` on a brand-new Markdown workspace only (never under `profile/`).\n * Does not overwrite existing files.\n */\nexport function seedAgentProfileMarkdownFiles(\n profileDir: string,\n markdownWorkspaceDir: string,\n options?: SeedWorkspaceProfileMarkdownOptions,\n): void {\n const wsPreExisted = existsSync(markdownWorkspaceDir);\n mkdirSync(profileDir, { recursive: true });\n mkdirSync(markdownWorkspaceDir, { recursive: true });\n\n const isBrandNewWorkspace = !wsPreExisted;\n\n let seeded = 0;\n for (const name of SEED_FILENAMES) {\n const targetPath = join(profileDir, name);\n const tpl = readTemplate(name);\n if (!tpl) {\n log.warn({ name }, 'Missing workspace template file; skip seeding');\n continue;\n }\n const body =\n name === WORKSPACE_FILES.IDENTITY ? personalizeIdentityTemplate(tpl, options?.displayName) : tpl;\n if (writeFileIfMissing(targetPath, body)) {\n seeded++;\n }\n }\n\n if (seeded > 0) {\n log.info({ profileDir, seeded }, 'Seeded profile Markdown files');\n }\n\n ensureGitRepo(markdownWorkspaceDir, isBrandNewWorkspace);\n}\n\n/** Attempt `git init` on a brand-new Markdown workspace; silently skip on failure. */\nfunction ensureGitRepo(markdownWorkspaceDir: string, isBrandNew: boolean): void {\n if (!isBrandNew) {\n return;\n }\n if (existsSync(join(markdownWorkspaceDir, '.git'))) {\n return;\n }\n try {\n execFileSync('git', ['init'], { cwd: markdownWorkspaceDir, stdio: 'ignore', timeout: 5_000 });\n log.info({ markdownWorkspaceDir }, 'Initialized git repo in Markdown workspace');\n } catch {\n log.debug({ markdownWorkspaceDir }, 'git init skipped (git not available or failed)');\n }\n}\n\n/**\n * Ensure default (`main`) agent has reference profile Markdown templates (missing files only).\n */\nexport function seedMainAgentProfileMarkdown(cfg: Config): void {\n const agentId = resolveDefaultAgentId(cfg);\n seedAgentProfileMarkdownFiles(\n resolveAgentProfileDir(cfg, agentId),\n resolveAgentWorkspaceDir(cfg, agentId),\n { displayName: agentId },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;kBAe2B;YAC6B;aAEH;AAErD,MAAM,MAAM,aAAa,gBAAgB;;AAGzC,MAAa,4BAA4B;AAOzC,MAAM,iBAAoC,CAAC,GAAG,oCAAoC;AAElF,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,6BAAqC;AAC5C,QAAO,KAAK,WAAW,sBAAsB;;;AAI/C,SAAS,kCAAiD;CACxD,IAAI,MAAM;AACV,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,YAAY,KAAK,KAAK,QAAQ,aAAa,YAAY;AAC7D,MAAI,WAAW,UAAU,CACvB,QAAO;EAET,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,QAAO;;;AAIT,SAAS,iCAAgD;CACvD,MAAM,UAAU,QAAQ,IAAI,oBAAoB,MAAM;AACtD,KAAI,WAAW,WAAW,QAAQ,CAChC,QAAO;AAET,QAAO,iCAAiC;;AAG1C,SAAS,aAAa,MAA6B;CACjD,MAAM,UAAU,gCAAgC;AAChD,KAAI,SAAS;EACX,MAAM,IAAI,KAAK,SAAS,KAAK;AAC7B,MAAI,WAAW,EAAE,CACf,QAAO,aAAa,GAAG,QAAQ;;CAGnC,MAAM,UAAU,KAAK,4BAA4B,EAAE,KAAK;AACxD,KAAI,WAAW,QAAQ,CACrB,QAAO,aAAa,SAAS,QAAQ;AAEvC,QAAO;;AAGT,SAAS,mBAAmB,YAAoB,SAA0B;AACxE,KAAI,WAAW,WAAW,CACxB,QAAO;AAET,eAAc,YAAY,SAAS,QAAQ;AAC3C,QAAO;;AAGT,SAAS,4BAA4B,SAAiB,aAA8B;CAClF,MAAM,IAAI,aAAa,MAAM;AAC7B,KAAI,CAAC,KAAK,CAAC,QAAQ,SAAA,8BAAmC,CACpD,QAAO;AAET,QAAO,QAAQ,WAAW,2BAA2B,EAAE;;;;;;;AAQzD,SAAgB,8BACd,YACA,sBACA,SACM;CACN,MAAM,eAAe,WAAW,qBAAqB;AACrD,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAC1C,WAAU,sBAAsB,EAAE,WAAW,MAAM,CAAC;CAEpD,MAAM,sBAAsB,CAAC;CAE7B,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,gBAAgB;EACjC,MAAM,aAAa,KAAK,YAAY,KAAK;EACzC,MAAM,MAAM,aAAa,KAAK;AAC9B,MAAI,CAAC,KAAK;AACR,OAAI,KAAK,EAAE,MAAM,EAAE,gDAAgD;AACnE;;AAIF,MAAI,mBAAmB,YADrB,SAAS,gBAAgB,WAAW,4BAA4B,KAAK,SAAS,YAAY,GAAG,IACvD,CACtC;;AAIJ,KAAI,SAAS,EACX,KAAI,KAAK;EAAE;EAAY;EAAQ,EAAE,gCAAgC;AAGnE,eAAc,sBAAsB,oBAAoB;;;AAI1D,SAAS,cAAc,sBAA8B,YAA2B;AAC9E,KAAI,CAAC,WACH;AAEF,KAAI,WAAW,KAAK,sBAAsB,OAAO,CAAC,CAChD;AAEF,KAAI;AACF,eAAa,OAAO,CAAC,OAAO,EAAE;GAAE,KAAK;GAAsB,OAAO;GAAU,SAAS;GAAO,CAAC;AAC7F,MAAI,KAAK,EAAE,sBAAsB,EAAE,6CAA6C;SAC1E;AACN,MAAI,MAAM,EAAE,sBAAsB,EAAE,iDAAiD;;;;;;AAOzF,SAAgB,6BAA6B,KAAmB;CAC9D,MAAM,UAAU,sBAAsB,IAAI;AAC1C,+BACE,uBAAuB,KAAK,QAAQ,EACpC,yBAAyB,KAAK,QAAQ,EACtC,EAAE,aAAa,SAAS,CACzB"}
@@ -1,13 +1,6 @@
1
1
  /**
2
- * Workspace bootstrap state machine — OpenClaw-aligned.
3
- *
4
- * Tracks BOOTSTRAP.md lifecycle in `<markdownWorkspace>/.xopc/workspace.json`
2
+ * Workspace setup metadata in `<markdownWorkspace>/.xopc/workspace.json`
5
3
  * (same path as `resolveWorkspaceStatePath` / `xopc init`).
6
- *
7
- * Motivation: BOOTSTRAP.md instructs the agent to delete itself after setup,
8
- * but that is unreliable (LLM-dependent). This module provides a runtime-level
9
- * state machine so the system can track whether bootstrap was completed
10
- * regardless of whether the agent actually deleted the file.
11
4
  */
12
5
  import type { Config } from '../../config/schema.js';
13
6
  import { resolveWorkspaceStatePath } from '../../config/paths.js';
@@ -18,35 +11,10 @@ export interface WorkspaceSetupState {
18
11
  agentId?: string;
19
12
  /** ISO timestamp when profile Markdown was first seeded (init / agents add). */
20
13
  profileMarkdownSeededAt?: string;
21
- /** ISO timestamp when BOOTSTRAP.md was first seeded into the profile. */
22
- bootstrapSeededAt?: string;
23
- /** ISO timestamp when bootstrap setup was completed (BOOTSTRAP.md workflow finished). */
24
- setupCompletedAt?: string;
25
14
  }
26
15
  /** Resolve workspace state path when only the Markdown workspace root is known. */
27
16
  export declare function resolveWorkspaceStatePathForMarkdownWorkspace(markdownWorkspaceDir: string): string;
28
17
  /** Resolve workspace state path from config + agent id. */
29
18
  export declare function resolveAgentWorkspaceStatePath(config: Config, agentId: string): string;
30
- /**
31
- * Check whether the workspace bootstrap setup has been completed.
32
- * Returns true when `setupCompletedAt` is set in the state file.
33
- */
34
- export declare function isWorkspaceSetupCompleted(statePath: string): boolean;
35
- /**
36
- * Check whether BOOTSTRAP.md is still pending (seeded but not completed).
37
- * Works even if the file was already deleted by the agent — the state file is authoritative.
38
- */
39
- export declare function isWorkspaceBootstrapPending(statePath: string): boolean;
40
- /**
41
- * When BOOTSTRAP.md was seeded but the file is now gone, treat setup as completed.
42
- * Idempotent; safe to call before loading bootstrap files or listing profile files.
43
- */
44
- export declare function syncBootstrapSetupCompletion(statePath: string, profileDir: string): void;
45
- /**
46
- * Mark BOOTSTRAP.md as seeded (call when workspace-seed.ts writes the file).
47
- */
48
- export declare function markBootstrapSeeded(statePath: string): void;
49
- /**
50
- * Mark bootstrap setup as completed.
51
- */
52
- export declare function markSetupCompleted(statePath: string): void;
19
+ /** Record profile Markdown seed time (idempotent). */
20
+ export declare function markProfileMarkdownSeeded(statePath: string): void;
@@ -1,17 +1,10 @@
1
- import { FILENAMES, WORKSPACE_FILES, init_paths, resolveWorkspaceStatePath } from "../../config/paths.js";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { FILENAMES, init_paths, resolveWorkspaceStatePath } from "../../config/paths.js";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  //#region src/agent/context/workspace-state.ts
5
5
  /**
6
- * Workspace bootstrap state machine — OpenClaw-aligned.
7
- *
8
- * Tracks BOOTSTRAP.md lifecycle in `<markdownWorkspace>/.xopc/workspace.json`
6
+ * Workspace setup metadata in `<markdownWorkspace>/.xopc/workspace.json`
9
7
  * (same path as `resolveWorkspaceStatePath` / `xopc init`).
10
- *
11
- * Motivation: BOOTSTRAP.md instructs the agent to delete itself after setup,
12
- * but that is unreliable (LLM-dependent). This module provides a runtime-level
13
- * state machine so the system can track whether bootstrap was completed
14
- * regardless of whether the agent actually deleted the file.
15
8
  */
16
9
  init_paths();
17
10
  const WORKSPACE_STATE_VERSION = 1;
@@ -22,9 +15,7 @@ function parseWorkspaceState(raw) {
22
15
  return {
23
16
  version: WORKSPACE_STATE_VERSION,
24
17
  agentId: typeof parsed.agentId === "string" ? parsed.agentId : void 0,
25
- profileMarkdownSeededAt: typeof parsed.profileMarkdownSeededAt === "string" ? parsed.profileMarkdownSeededAt : void 0,
26
- bootstrapSeededAt: typeof parsed.bootstrapSeededAt === "string" ? parsed.bootstrapSeededAt : void 0,
27
- setupCompletedAt: typeof parsed.setupCompletedAt === "string" ? parsed.setupCompletedAt : void 0
18
+ profileMarkdownSeededAt: typeof parsed.profileMarkdownSeededAt === "string" ? parsed.profileMarkdownSeededAt : void 0
28
19
  };
29
20
  } catch {
30
21
  return null;
@@ -55,47 +46,12 @@ function resolveWorkspaceStatePathForMarkdownWorkspace(markdownWorkspaceDir) {
55
46
  function resolveAgentWorkspaceStatePath(config, agentId) {
56
47
  return resolveWorkspaceStatePath(config, agentId);
57
48
  }
58
- /**
59
- * Check whether the workspace bootstrap setup has been completed.
60
- * Returns true when `setupCompletedAt` is set in the state file.
61
- */
62
- function isWorkspaceSetupCompleted(statePath) {
63
- const state = readWorkspaceState(statePath);
64
- return typeof state.setupCompletedAt === "string" && state.setupCompletedAt.trim().length > 0;
65
- }
66
- /**
67
- * Check whether BOOTSTRAP.md is still pending (seeded but not completed).
68
- * Works even if the file was already deleted by the agent — the state file is authoritative.
69
- */
70
- function isWorkspaceBootstrapPending(statePath) {
71
- const state = readWorkspaceState(statePath);
72
- if (typeof state.setupCompletedAt === "string" && state.setupCompletedAt.trim().length > 0) return false;
73
- return typeof state.bootstrapSeededAt === "string";
74
- }
75
- /**
76
- * When BOOTSTRAP.md was seeded but the file is now gone, treat setup as completed.
77
- * Idempotent; safe to call before loading bootstrap files or listing profile files.
78
- */
79
- function syncBootstrapSetupCompletion(statePath, profileDir) {
80
- if (isWorkspaceSetupCompleted(statePath)) return;
81
- if (existsSync(join(profileDir, WORKSPACE_FILES.BOOTSTRAP))) return;
82
- if (!readWorkspaceState(statePath).bootstrapSeededAt) return;
83
- markSetupCompleted(statePath);
84
- }
85
- /**
86
- * Mark BOOTSTRAP.md as seeded (call when workspace-seed.ts writes the file).
87
- */
88
- function markBootstrapSeeded(statePath) {
89
- if (readWorkspaceState(statePath).bootstrapSeededAt) return;
90
- writeWorkspaceState(statePath, { bootstrapSeededAt: (/* @__PURE__ */ new Date()).toISOString() });
91
- }
92
- /**
93
- * Mark bootstrap setup as completed.
94
- */
95
- function markSetupCompleted(statePath) {
96
- writeWorkspaceState(statePath, { setupCompletedAt: (/* @__PURE__ */ new Date()).toISOString() });
49
+ /** Record profile Markdown seed time (idempotent). */
50
+ function markProfileMarkdownSeeded(statePath) {
51
+ if (readWorkspaceState(statePath).profileMarkdownSeededAt) return;
52
+ writeWorkspaceState(statePath, { profileMarkdownSeededAt: (/* @__PURE__ */ new Date()).toISOString() });
97
53
  }
98
54
  //#endregion
99
- export { isWorkspaceBootstrapPending, isWorkspaceSetupCompleted, markBootstrapSeeded, markSetupCompleted, resolveAgentWorkspaceStatePath, resolveWorkspaceStatePath, resolveWorkspaceStatePathForMarkdownWorkspace, syncBootstrapSetupCompletion };
55
+ export { markProfileMarkdownSeeded, resolveAgentWorkspaceStatePath, resolveWorkspaceStatePath, resolveWorkspaceStatePathForMarkdownWorkspace };
100
56
 
101
57
  //# sourceMappingURL=workspace-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-state.js","names":[],"sources":["../../../../src/agent/context/workspace-state.ts"],"sourcesContent":["/**\n * Workspace bootstrap state machine — OpenClaw-aligned.\n *\n * Tracks BOOTSTRAP.md lifecycle in `<markdownWorkspace>/.xopc/workspace.json`\n * (same path as `resolveWorkspaceStatePath` / `xopc init`).\n *\n * Motivation: BOOTSTRAP.md instructs the agent to delete itself after setup,\n * but that is unreliable (LLM-dependent). This module provides a runtime-level\n * state machine so the system can track whether bootstrap was completed\n * regardless of whether the agent actually deleted the file.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport type { Config } from '../../config/schema.js';\nimport { FILENAMES, WORKSPACE_FILES, resolveWorkspaceStatePath } from '../../config/paths.js';\n\nexport { resolveWorkspaceStatePath };\n\nconst WORKSPACE_STATE_VERSION = 1;\n\nexport interface WorkspaceSetupState {\n version: number;\n /** Set by `xopc init` for the agent id owning this workspace. */\n agentId?: string;\n /** ISO timestamp when profile Markdown was first seeded (init / agents add). */\n profileMarkdownSeededAt?: string;\n /** ISO timestamp when BOOTSTRAP.md was first seeded into the profile. */\n bootstrapSeededAt?: string;\n /** ISO timestamp when bootstrap setup was completed (BOOTSTRAP.md workflow finished). */\n setupCompletedAt?: string;\n}\n\nfunction parseWorkspaceState(raw: string): WorkspaceSetupState | null {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (!parsed || typeof parsed !== 'object') {\n return null;\n }\n return {\n version: WORKSPACE_STATE_VERSION,\n agentId: typeof parsed.agentId === 'string' ? parsed.agentId : undefined,\n profileMarkdownSeededAt:\n typeof parsed.profileMarkdownSeededAt === 'string' ? parsed.profileMarkdownSeededAt : undefined,\n bootstrapSeededAt:\n typeof parsed.bootstrapSeededAt === 'string' ? parsed.bootstrapSeededAt : undefined,\n setupCompletedAt: typeof parsed.setupCompletedAt === 'string' ? parsed.setupCompletedAt : undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction readWorkspaceState(statePath: string): WorkspaceSetupState {\n try {\n const raw = readFileSync(statePath, 'utf-8');\n return parseWorkspaceState(raw) ?? { version: WORKSPACE_STATE_VERSION };\n } catch (err: unknown) {\n const code = (err as { code?: string }).code;\n if (code !== 'ENOENT') {\n throw err;\n }\n return { version: WORKSPACE_STATE_VERSION };\n }\n}\n\nfunction writeWorkspaceState(statePath: string, patch: Partial<WorkspaceSetupState>): void {\n const merged = { ...readWorkspaceState(statePath), ...patch, version: WORKSPACE_STATE_VERSION };\n mkdirSync(dirname(statePath), { recursive: true });\n writeFileSync(statePath, `${JSON.stringify(merged, null, 2)}\\n`, 'utf-8');\n}\n\n/** Resolve workspace state path when only the Markdown workspace root is known. */\nexport function resolveWorkspaceStatePathForMarkdownWorkspace(markdownWorkspaceDir: string): string {\n return join(markdownWorkspaceDir, '.xopc', FILENAMES.WORKSPACE_STATE);\n}\n\n/** Resolve workspace state path from config + agent id. */\nexport function resolveAgentWorkspaceStatePath(config: Config, agentId: string): string {\n return resolveWorkspaceStatePath(config, agentId);\n}\n\n/**\n * Check whether the workspace bootstrap setup has been completed.\n * Returns true when `setupCompletedAt` is set in the state file.\n */\nexport function isWorkspaceSetupCompleted(statePath: string): boolean {\n const state = readWorkspaceState(statePath);\n return typeof state.setupCompletedAt === 'string' && state.setupCompletedAt.trim().length > 0;\n}\n\n/**\n * Check whether BOOTSTRAP.md is still pending (seeded but not completed).\n * Works even if the file was already deleted by the agent — the state file is authoritative.\n */\nexport function isWorkspaceBootstrapPending(statePath: string): boolean {\n const state = readWorkspaceState(statePath);\n if (typeof state.setupCompletedAt === 'string' && state.setupCompletedAt.trim().length > 0) {\n return false;\n }\n return typeof state.bootstrapSeededAt === 'string';\n}\n\n/**\n * When BOOTSTRAP.md was seeded but the file is now gone, treat setup as completed.\n * Idempotent; safe to call before loading bootstrap files or listing profile files.\n */\nexport function syncBootstrapSetupCompletion(statePath: string, profileDir: string): void {\n if (isWorkspaceSetupCompleted(statePath)) {\n return;\n }\n if (existsSync(join(profileDir, WORKSPACE_FILES.BOOTSTRAP))) {\n return;\n }\n const state = readWorkspaceState(statePath);\n if (!state.bootstrapSeededAt) {\n return;\n }\n markSetupCompleted(statePath);\n}\n\n/**\n * Mark BOOTSTRAP.md as seeded (call when workspace-seed.ts writes the file).\n */\nexport function markBootstrapSeeded(statePath: string): void {\n const state = readWorkspaceState(statePath);\n if (state.bootstrapSeededAt) {\n return;\n }\n writeWorkspaceState(statePath, { bootstrapSeededAt: new Date().toISOString() });\n}\n\n/**\n * Mark bootstrap setup as completed.\n */\nexport function markSetupCompleted(statePath: string): void {\n writeWorkspaceState(statePath, { setupCompletedAt: new Date().toISOString() });\n}\n"],"mappings":";;;;;;;;;;;;;;;YAgB8F;AAI9F,MAAM,0BAA0B;AAchC,SAAS,oBAAoB,KAAyC;AACpE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,CAAC,UAAU,OAAO,WAAW,SAC/B,QAAO;AAET,SAAO;GACL,SAAS;GACT,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,KAAA;GAC/D,yBACE,OAAO,OAAO,4BAA4B,WAAW,OAAO,0BAA0B,KAAA;GACxF,mBACE,OAAO,OAAO,sBAAsB,WAAW,OAAO,oBAAoB,KAAA;GAC5E,kBAAkB,OAAO,OAAO,qBAAqB,WAAW,OAAO,mBAAmB,KAAA;GAC3F;SACK;AACN,SAAO;;;AAIX,SAAS,mBAAmB,WAAwC;AAClE,KAAI;AAEF,SAAO,oBADK,aAAa,WAAW,QACN,CAAC,IAAI,EAAE,SAAS,yBAAyB;UAChE,KAAc;AAErB,MADc,IAA0B,SAC3B,SACX,OAAM;AAER,SAAO,EAAE,SAAS,yBAAyB;;;AAI/C,SAAS,oBAAoB,WAAmB,OAA2C;CACzF,MAAM,SAAS;EAAE,GAAG,mBAAmB,UAAU;EAAE,GAAG;EAAO,SAAS;EAAyB;AAC/F,WAAU,QAAQ,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AAClD,eAAc,WAAW,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,KAAK,QAAQ;;;AAI3E,SAAgB,8CAA8C,sBAAsC;AAClG,QAAO,KAAK,sBAAsB,SAAS,UAAU,gBAAgB;;;AAIvE,SAAgB,+BAA+B,QAAgB,SAAyB;AACtF,QAAO,0BAA0B,QAAQ,QAAQ;;;;;;AAOnD,SAAgB,0BAA0B,WAA4B;CACpE,MAAM,QAAQ,mBAAmB,UAAU;AAC3C,QAAO,OAAO,MAAM,qBAAqB,YAAY,MAAM,iBAAiB,MAAM,CAAC,SAAS;;;;;;AAO9F,SAAgB,4BAA4B,WAA4B;CACtE,MAAM,QAAQ,mBAAmB,UAAU;AAC3C,KAAI,OAAO,MAAM,qBAAqB,YAAY,MAAM,iBAAiB,MAAM,CAAC,SAAS,EACvF,QAAO;AAET,QAAO,OAAO,MAAM,sBAAsB;;;;;;AAO5C,SAAgB,6BAA6B,WAAmB,YAA0B;AACxF,KAAI,0BAA0B,UAAU,CACtC;AAEF,KAAI,WAAW,KAAK,YAAY,gBAAgB,UAAU,CAAC,CACzD;AAGF,KAAI,CADU,mBAAmB,UACvB,CAAC,kBACT;AAEF,oBAAmB,UAAU;;;;;AAM/B,SAAgB,oBAAoB,WAAyB;AAE3D,KADc,mBAAmB,UACxB,CAAC,kBACR;AAEF,qBAAoB,WAAW,EAAE,oCAAmB,IAAI,MAAM,EAAC,aAAa,EAAE,CAAC;;;;;AAMjF,SAAgB,mBAAmB,WAAyB;AAC1D,qBAAoB,WAAW,EAAE,mCAAkB,IAAI,MAAM,EAAC,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"workspace-state.js","names":[],"sources":["../../../../src/agent/context/workspace-state.ts"],"sourcesContent":["/**\n * Workspace setup metadata in `<markdownWorkspace>/.xopc/workspace.json`\n * (same path as `resolveWorkspaceStatePath` / `xopc init`).\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport type { Config } from '../../config/schema.js';\nimport { FILENAMES, resolveWorkspaceStatePath } from '../../config/paths.js';\n\nexport { resolveWorkspaceStatePath };\n\nconst WORKSPACE_STATE_VERSION = 1;\n\nexport interface WorkspaceSetupState {\n version: number;\n /** Set by `xopc init` for the agent id owning this workspace. */\n agentId?: string;\n /** ISO timestamp when profile Markdown was first seeded (init / agents add). */\n profileMarkdownSeededAt?: string;\n}\n\nfunction parseWorkspaceState(raw: string): WorkspaceSetupState | null {\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (!parsed || typeof parsed !== 'object') {\n return null;\n }\n return {\n version: WORKSPACE_STATE_VERSION,\n agentId: typeof parsed.agentId === 'string' ? parsed.agentId : undefined,\n profileMarkdownSeededAt:\n typeof parsed.profileMarkdownSeededAt === 'string' ? parsed.profileMarkdownSeededAt : undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction readWorkspaceState(statePath: string): WorkspaceSetupState {\n try {\n const raw = readFileSync(statePath, 'utf-8');\n return parseWorkspaceState(raw) ?? { version: WORKSPACE_STATE_VERSION };\n } catch (err: unknown) {\n const code = (err as { code?: string }).code;\n if (code !== 'ENOENT') {\n throw err;\n }\n return { version: WORKSPACE_STATE_VERSION };\n }\n}\n\nfunction writeWorkspaceState(statePath: string, patch: Partial<WorkspaceSetupState>): void {\n const merged = { ...readWorkspaceState(statePath), ...patch, version: WORKSPACE_STATE_VERSION };\n mkdirSync(dirname(statePath), { recursive: true });\n writeFileSync(statePath, `${JSON.stringify(merged, null, 2)}\\n`, 'utf-8');\n}\n\n/** Resolve workspace state path when only the Markdown workspace root is known. */\nexport function resolveWorkspaceStatePathForMarkdownWorkspace(markdownWorkspaceDir: string): string {\n return join(markdownWorkspaceDir, '.xopc', FILENAMES.WORKSPACE_STATE);\n}\n\n/** Resolve workspace state path from config + agent id. */\nexport function resolveAgentWorkspaceStatePath(config: Config, agentId: string): string {\n return resolveWorkspaceStatePath(config, agentId);\n}\n\n/** Record profile Markdown seed time (idempotent). */\nexport function markProfileMarkdownSeeded(statePath: string): void {\n const state = readWorkspaceState(statePath);\n if (state.profileMarkdownSeededAt) {\n return;\n }\n writeWorkspaceState(statePath, { profileMarkdownSeededAt: new Date().toISOString() });\n}\n"],"mappings":";;;;;;;;YAS6E;AAI7E,MAAM,0BAA0B;AAUhC,SAAS,oBAAoB,KAAyC;AACpE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,CAAC,UAAU,OAAO,WAAW,SAC/B,QAAO;AAET,SAAO;GACL,SAAS;GACT,SAAS,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,KAAA;GAC/D,yBACE,OAAO,OAAO,4BAA4B,WAAW,OAAO,0BAA0B,KAAA;GACzF;SACK;AACN,SAAO;;;AAIX,SAAS,mBAAmB,WAAwC;AAClE,KAAI;AAEF,SAAO,oBADK,aAAa,WAAW,QACN,CAAC,IAAI,EAAE,SAAS,yBAAyB;UAChE,KAAc;AAErB,MADc,IAA0B,SAC3B,SACX,OAAM;AAER,SAAO,EAAE,SAAS,yBAAyB;;;AAI/C,SAAS,oBAAoB,WAAmB,OAA2C;CACzF,MAAM,SAAS;EAAE,GAAG,mBAAmB,UAAU;EAAE,GAAG;EAAO,SAAS;EAAyB;AAC/F,WAAU,QAAQ,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AAClD,eAAc,WAAW,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,KAAK,QAAQ;;;AAI3E,SAAgB,8CAA8C,sBAAsC;AAClG,QAAO,KAAK,sBAAsB,SAAS,UAAU,gBAAgB;;;AAIvE,SAAgB,+BAA+B,QAAgB,SAAyB;AACtF,QAAO,0BAA0B,QAAQ,QAAQ;;;AAInD,SAAgB,0BAA0B,WAAyB;AAEjE,KADc,mBAAmB,UACxB,CAAC,wBACR;AAEF,qBAAoB,WAAW,EAAE,0CAAyB,IAAI,MAAM,EAAC,aAAa,EAAE,CAAC"}
@@ -9,10 +9,6 @@ read_when:
9
9
 
10
10
  This folder is home. Treat it that way.
11
11
 
12
- ## First Run
13
-
14
- If `BOOTSTRAP.md` exists, that's your birth certificate. Follow it, figure out who you are, then delete it. You won't need it again.
15
-
16
12
  ## Session Startup
17
13
 
18
14
  Use runtime-provided startup context first.
@@ -7,7 +7,6 @@ const CONTEXT_FILE_ORDER = new Map([
7
7
  ["identity.md", 30],
8
8
  ["user.md", 40],
9
9
  ["tools.md", 50],
10
- ["bootstrap.md", 60],
11
10
  ["memory.md", 70]
12
11
  ]);
13
12
  const DYNAMIC_CONTEXT_FILE_BASENAMES = new Set(["heartbeat.md"]);
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.js","names":[],"sources":["../../../../src/agent/prompt/system-prompt.ts"],"sourcesContent":["/**\n * System Prompt Builder — xopc-owned prompt with OpenClaw-style bootstrap injection.\n *\n * Profile Markdown under `agents/<id>/profile/` is injected as Project Context\n * (see `src/agent/bootstrap/`). Runtime owns loading; AGENTS.md instructs agents\n * not to manually reread startup files.\n */\n\nimport type { EmbeddedContextFile } from '../bootstrap/types.js';\nimport { DEFAULT_HEARTBEAT_FILENAME } from '../context/workspace.js';\nimport { PROMPT_CACHE_BOUNDARY } from './cache-boundary.js';\nimport type { PromptMode } from './types.js';\n\nexport type MemoryCitationsMode = 'on' | 'off' | 'source-only';\n\nconst CONTEXT_FILE_ORDER = new Map<string, number>([\n ['agents.md', 10],\n ['soul.md', 20],\n ['identity.md', 30],\n ['user.md', 40],\n ['tools.md', 50],\n ['bootstrap.md', 60],\n ['memory.md', 70],\n]);\n\nconst DYNAMIC_CONTEXT_FILE_BASENAMES = new Set(['heartbeat.md']);\n\nconst DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK =\n 'Default heartbeat prompt:\\n`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`';\n\nfunction normalizeContextFilePath(pathValue: string): string {\n return pathValue.trim().replace(/\\\\/g, '/');\n}\n\nfunction getContextFileBasename(pathValue: string): string {\n const normalizedPath = normalizeContextFilePath(pathValue);\n return (normalizedPath.split('/').pop() ?? normalizedPath).toLowerCase();\n}\n\nfunction isDynamicContextFile(pathValue: string): boolean {\n return DYNAMIC_CONTEXT_FILE_BASENAMES.has(getContextFileBasename(pathValue));\n}\n\nfunction sanitizeContextFileContentForPrompt(content: string): string {\n return content.replaceAll(DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK, '').replace(/\\n{3,}/g, '\\n\\n');\n}\n\nfunction sortContextFilesForPrompt(contextFiles: EmbeddedContextFile[]): EmbeddedContextFile[] {\n return [...contextFiles].sort((a, b) => {\n const aBase = getContextFileBasename(a.path);\n const bBase = getContextFileBasename(b.path);\n const aOrder = CONTEXT_FILE_ORDER.get(aBase) ?? Number.MAX_SAFE_INTEGER;\n const bOrder = CONTEXT_FILE_ORDER.get(bBase) ?? Number.MAX_SAFE_INTEGER;\n if (aOrder !== bOrder) {\n return aOrder - bOrder;\n }\n if (aBase !== bBase) {\n return aBase.localeCompare(bBase);\n }\n return normalizeContextFilePath(a.path).localeCompare(normalizeContextFilePath(b.path));\n });\n}\n\nfunction buildProjectContextSection(params: {\n files: EmbeddedContextFile[];\n heading: string;\n dynamic: boolean;\n}): string[] {\n if (params.files.length === 0) {\n return [];\n }\n const lines: string[] = [params.heading, ''];\n if (params.dynamic) {\n lines.push(\n 'The following frequently-changing project context files are kept below the cache boundary when possible:',\n '',\n );\n } else {\n const hasSoulFile = params.files.some((file) => getContextFileBasename(file.path) === 'soul.md');\n lines.push('The following project context files have been loaded:');\n if (hasSoulFile) {\n lines.push(\n 'If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.',\n );\n }\n lines.push('');\n }\n for (const file of params.files) {\n lines.push(`## ${file.path}`, '', sanitizeContextFileContentForPrompt(file.content), '');\n }\n return lines;\n}\n\nfunction buildMemorySection(\n citationsMode: MemoryCitationsMode = 'on',\n hasProfileMemory = false,\n): string {\n if (!hasProfileMemory) {\n return '';\n }\n\n const citationInstruction =\n citationsMode === 'off'\n ? 'Citations are disabled: do not mention file paths or line numbers in replies.'\n : citationsMode === 'source-only'\n ? 'Citations: mention file path when it helps (e.g., Source: MEMORY.md).'\n : 'Citations: include Source: <path#line> when it helps the user verify memory snippets.';\n\n return `## Memory Recall\n\n${citationInstruction}\n\nStartup profile files (SOUL, USER, MEMORY, etc.) are already in Project Context above. Do not re-read them unless the user asks or you need lines beyond what was injected.\n\nBefore answering anything about prior work, decisions, dates, people, preferences, or todos:\n1. Run \\`memory_search\\` on profile MEMORY.md and workspace \\`memory/*.md\\`\n2. For **other chat sessions** / cross-session history, use \\`session_search\\` with keywords (or omit \\`query\\` to list recent sessions)\n3. Use \\`memory_get\\` to pull only the needed lines from files\n4. For structured curated notes under agent home \\`memories/\\`, use \\`curated_memory\\`\n5. If low confidence after search, say you checked\n\n### Memory Files\n\n- **Daily notes:** \\`memory/YYYY-MM-DD.md\\` — raw logs (runtime may preload recent days on /new or /reset)\n- **Long-term:** profile \\`MEMORY.md\\` — curated memories in Project Context when present\n- **Curated store:** agent home \\`memories/MEMORY.md\\` and \\`memories/USER.md\\` — use \\`curated_memory\\` for live read/write\n\n### Writing to Memory\n\n- **Declarative vs procedural:** Save facts and preferences via workspace memory files and/or \\`curated_memory\\`. Save reusable task playbooks with \\`skill_manage\\` as skills.\n- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE\n- When someone says \"remember this\" → update \\`memory/YYYY-MM-DD.md\\` or relevant file\n- **Text > Brain**\n`;\n}\n\nfunction buildSkillsSection(availableTools: string[] = []): string {\n if (availableTools.length === 0) {\n return '';\n }\n\n return `## Skills\n\nWhen a solution already exists, do not reinvent the wheel.\n\n**How to use:**\n1. Skim <available_skills> — is anything clearly relevant?\n2. Only one match? → Confirm with skills_list, then load the full text with skill_view(name) and follow it.\n3. Need sub-documents or scripts? → skill_view(name, \"references/…\"), etc.\n4. No match? → Solve it yourself; do not force-fit a skill.\n\n**Division of labor with memory:** Skills = **procedural** workflows; memory / \\`curated_memory\\` = **declarative** facts and preferences.\n`;\n}\n\nfunction buildSafetySection(): string {\n return `## Safety\n\n- You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request.\n- Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards.\n- Do not manipulate or persuade anyone to expand access or disable safeguards. Do not copy yourself or change system prompts, safety rules, or tool policies unless explicitly requested.`;\n}\n\nfunction buildProblemSolvingSection(): string {\n return `## Problem Solving\n\n**Simple tasks** (< 5 minutes or a single-file change): Do them directly; run a quick verification after changes.\n\n**Complex tasks** (multiple files or design decisions): Use an iterative flow — Plan → Build → Verify → Fix.\n\n**Core principle: Match the complexity; reject ritual for its own sake. Verification matters, but do not verify just to tick a box.**`;\n}\n\nfunction buildAestheticSection(): string {\n return `## Tone & Style\n\n**Default voice:** Direct, concise, concrete.\n\n**SOUL.md takes precedence:** If SOUL.md defines a specific tone, defer to it over the above.`;\n}\n\nfunction buildHeartbeatBehaviorSection(params: {\n enabled: boolean;\n customPrompt?: string;\n userTimezone?: string;\n}): string {\n if (!params.enabled) {\n return '';\n }\n if (params.customPrompt?.trim()) {\n return `## Heartbeats\\n\\n${params.customPrompt.trim()}\\n`;\n }\n let quietHoursNote = '';\n if (params.userTimezone) {\n quietHoursNote = `\\n\\n> Quiet hours: The user is in **${params.userTimezone}**. Avoid proactive checks during late night (23:00-08:00) unless urgent.`;\n }\n return `## Heartbeats\n\nIf the current user message is a heartbeat poll and nothing needs attention, reply exactly: HEARTBEAT_OK\n\nIf something needs attention, do NOT include \"HEARTBEAT_OK\"; reply with the alert text instead.${quietHoursNote}\n`;\n}\n\nfunction buildMessagingSection(channels: string[] = [], isMinimal: boolean = false): string {\n if (isMinimal || channels.length === 0) {\n return '';\n }\n const channelList = channels.join(', ');\n return `## Messaging\n\n- Reply in current session → automatically routes to the source channel (${channelList})\n- Use \\`message\\` for proactive sends + channel actions\n- If you use \\`message\\` to deliver your user-visible reply, respond with ONLY: NO_REPLY (avoid duplicate replies)\n`;\n}\n\nfunction buildTimeSection(timezone?: string): string {\n if (!timezone) {\n return '';\n }\n return `## Current Date & Time\n\nTime zone: ${timezone}\n\nIf you need the current date/time/day-of-week, use the \\`session_status\\` tool or the inbound message timestamp envelope (when present).\n`;\n}\n\nfunction buildRuntimeSection(runtime?: { version?: string; model?: string; channel?: string }): string {\n if (!runtime) {\n return '';\n }\n const parts: string[] = [];\n if (runtime.version) parts.push(`v${runtime.version}`);\n if (runtime.model) parts.push(`model=${runtime.model.split('/').pop()}`);\n if (runtime.channel) parts.push(`ch=${runtime.channel}`);\n return parts.length > 0 ? `[${parts.join(' | ')}]` : '';\n}\n\nfunction buildWorkingDirSection(workspaceDir: string): string {\n return `Working directory: ${workspaceDir}`;\n}\n\nfunction buildExternalMemorySection(text: string | undefined): string {\n const t = text?.trim();\n if (!t) {\n return '';\n }\n return `## External memory provider\\n\\n${t}`;\n}\n\nexport interface SystemPromptOptions {\n /** Bootstrap context files from profile Markdown (Project Context). */\n contextFiles?: EmbeddedContextFile[];\n promptMode?: PromptMode;\n heartbeatEnabled?: boolean;\n heartbeatPrompt?: string;\n availableTools?: string[];\n memoryCitationsMode?: MemoryCitationsMode;\n userTimezone?: string;\n runtime?: {\n version?: string;\n model?: string;\n channel?: string;\n };\n channels?: string[];\n externalMemoryInstructions?: string;\n ttsSystemHint?: string;\n}\n\n/**\n * Build system prompt with bootstrap Project Context integration.\n */\nexport function buildSystemPrompt(workspaceDir: string, options: SystemPromptOptions): string {\n const {\n contextFiles = [],\n promptMode = 'full',\n heartbeatEnabled = false,\n heartbeatPrompt,\n availableTools = [],\n memoryCitationsMode = 'on',\n userTimezone,\n runtime,\n channels = [],\n externalMemoryInstructions,\n ttsSystemHint,\n } = options;\n\n if (promptMode === 'none') {\n return 'You are a personal AI assistant running inside xopc.';\n }\n\n const isMinimal = promptMode === 'minimal';\n const orderedContextFiles = sortContextFilesForPrompt(\n contextFiles.filter((file) => file.path.trim().length > 0),\n );\n const stableContextFiles = orderedContextFiles.filter((file) => !isDynamicContextFile(file.path));\n const dynamicContextFiles = orderedContextFiles.filter((file) => isDynamicContextFile(file.path));\n const hasProfileMemory = orderedContextFiles.some(\n (file) => getContextFileBasename(file.path) === 'memory.md',\n );\n\n const sections: string[] = [\n 'You are a personal AI assistant running inside xopc.',\n '',\n '## Workspace Files (injected)',\n '',\n 'Profile bootstrap files are injected below as Project Context. Do not manually reread them at session start unless the user asks or injected content is insufficient.',\n '',\n ];\n\n if (!isMinimal) {\n sections.push(buildTimeSection(userTimezone));\n sections.push(buildExternalMemorySection(externalMemoryInstructions));\n sections.push(buildMemorySection(memoryCitationsMode, hasProfileMemory));\n }\n\n sections.push(buildSkillsSection(availableTools));\n\n if (!isMinimal) {\n sections.push(buildSafetySection());\n sections.push(buildProblemSolvingSection());\n sections.push(buildAestheticSection());\n }\n\n sections.push(\n ...buildProjectContextSection({\n files: stableContextFiles,\n heading: '# Project Context',\n dynamic: false,\n }),\n );\n\n sections.push(PROMPT_CACHE_BOUNDARY);\n\n sections.push(\n ...buildProjectContextSection({\n files: dynamicContextFiles,\n heading: stableContextFiles.length > 0 ? '# Dynamic Project Context' : '# Project Context',\n dynamic: true,\n }),\n );\n\n sections.push(buildHeartbeatBehaviorSection({ enabled: heartbeatEnabled, customPrompt: heartbeatPrompt, userTimezone }));\n sections.push(buildWorkingDirSection(workspaceDir));\n sections.push(buildMessagingSection(channels, isMinimal));\n\n if (!isMinimal && ttsSystemHint?.trim()) {\n sections.push(`## Voice (TTS)\\n\\n${ttsSystemHint.trim()}`);\n }\n\n sections.push(buildRuntimeSection(runtime));\n\n return sections.filter(Boolean).join('\\n\\n');\n}\n\n/** Whether HEARTBEAT.md is injected as dynamic context (vs behavior-only section). */\nexport function isHeartbeatContextFile(pathValue: string): boolean {\n return getContextFileBasename(pathValue) === DEFAULT_HEARTBEAT_FILENAME.toLowerCase();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"system-prompt.js","names":[],"sources":["../../../../src/agent/prompt/system-prompt.ts"],"sourcesContent":["/**\n * System Prompt Builder — xopc-owned prompt with OpenClaw-style bootstrap injection.\n *\n * Profile Markdown under `agents/<id>/profile/` is injected as Project Context\n * (see `src/agent/bootstrap/`). Runtime owns loading; AGENTS.md instructs agents\n * not to manually reread startup files.\n */\n\nimport type { EmbeddedContextFile } from '../bootstrap/types.js';\nimport { DEFAULT_HEARTBEAT_FILENAME } from '../context/workspace.js';\nimport { PROMPT_CACHE_BOUNDARY } from './cache-boundary.js';\nimport type { PromptMode } from './types.js';\n\nexport type MemoryCitationsMode = 'on' | 'off' | 'source-only';\n\nconst CONTEXT_FILE_ORDER = new Map<string, number>([\n ['agents.md', 10],\n ['soul.md', 20],\n ['identity.md', 30],\n ['user.md', 40],\n ['tools.md', 50],\n ['memory.md', 70],\n]);\n\nconst DYNAMIC_CONTEXT_FILE_BASENAMES = new Set(['heartbeat.md']);\n\nconst DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK =\n 'Default heartbeat prompt:\\n`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`';\n\nfunction normalizeContextFilePath(pathValue: string): string {\n return pathValue.trim().replace(/\\\\/g, '/');\n}\n\nfunction getContextFileBasename(pathValue: string): string {\n const normalizedPath = normalizeContextFilePath(pathValue);\n return (normalizedPath.split('/').pop() ?? normalizedPath).toLowerCase();\n}\n\nfunction isDynamicContextFile(pathValue: string): boolean {\n return DYNAMIC_CONTEXT_FILE_BASENAMES.has(getContextFileBasename(pathValue));\n}\n\nfunction sanitizeContextFileContentForPrompt(content: string): string {\n return content.replaceAll(DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK, '').replace(/\\n{3,}/g, '\\n\\n');\n}\n\nfunction sortContextFilesForPrompt(contextFiles: EmbeddedContextFile[]): EmbeddedContextFile[] {\n return [...contextFiles].sort((a, b) => {\n const aBase = getContextFileBasename(a.path);\n const bBase = getContextFileBasename(b.path);\n const aOrder = CONTEXT_FILE_ORDER.get(aBase) ?? Number.MAX_SAFE_INTEGER;\n const bOrder = CONTEXT_FILE_ORDER.get(bBase) ?? Number.MAX_SAFE_INTEGER;\n if (aOrder !== bOrder) {\n return aOrder - bOrder;\n }\n if (aBase !== bBase) {\n return aBase.localeCompare(bBase);\n }\n return normalizeContextFilePath(a.path).localeCompare(normalizeContextFilePath(b.path));\n });\n}\n\nfunction buildProjectContextSection(params: {\n files: EmbeddedContextFile[];\n heading: string;\n dynamic: boolean;\n}): string[] {\n if (params.files.length === 0) {\n return [];\n }\n const lines: string[] = [params.heading, ''];\n if (params.dynamic) {\n lines.push(\n 'The following frequently-changing project context files are kept below the cache boundary when possible:',\n '',\n );\n } else {\n const hasSoulFile = params.files.some((file) => getContextFileBasename(file.path) === 'soul.md');\n lines.push('The following project context files have been loaded:');\n if (hasSoulFile) {\n lines.push(\n 'If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.',\n );\n }\n lines.push('');\n }\n for (const file of params.files) {\n lines.push(`## ${file.path}`, '', sanitizeContextFileContentForPrompt(file.content), '');\n }\n return lines;\n}\n\nfunction buildMemorySection(\n citationsMode: MemoryCitationsMode = 'on',\n hasProfileMemory = false,\n): string {\n if (!hasProfileMemory) {\n return '';\n }\n\n const citationInstruction =\n citationsMode === 'off'\n ? 'Citations are disabled: do not mention file paths or line numbers in replies.'\n : citationsMode === 'source-only'\n ? 'Citations: mention file path when it helps (e.g., Source: MEMORY.md).'\n : 'Citations: include Source: <path#line> when it helps the user verify memory snippets.';\n\n return `## Memory Recall\n\n${citationInstruction}\n\nStartup profile files (SOUL, USER, MEMORY, etc.) are already in Project Context above. Do not re-read them unless the user asks or you need lines beyond what was injected.\n\nBefore answering anything about prior work, decisions, dates, people, preferences, or todos:\n1. Run \\`memory_search\\` on profile MEMORY.md and workspace \\`memory/*.md\\`\n2. For **other chat sessions** / cross-session history, use \\`session_search\\` with keywords (or omit \\`query\\` to list recent sessions)\n3. Use \\`memory_get\\` to pull only the needed lines from files\n4. For structured curated notes under agent home \\`memories/\\`, use \\`curated_memory\\`\n5. If low confidence after search, say you checked\n\n### Memory Files\n\n- **Daily notes:** \\`memory/YYYY-MM-DD.md\\` — raw logs (runtime may preload recent days on /new or /reset)\n- **Long-term:** profile \\`MEMORY.md\\` — curated memories in Project Context when present\n- **Curated store:** agent home \\`memories/MEMORY.md\\` and \\`memories/USER.md\\` — use \\`curated_memory\\` for live read/write\n\n### Writing to Memory\n\n- **Declarative vs procedural:** Save facts and preferences via workspace memory files and/or \\`curated_memory\\`. Save reusable task playbooks with \\`skill_manage\\` as skills.\n- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE\n- When someone says \"remember this\" → update \\`memory/YYYY-MM-DD.md\\` or relevant file\n- **Text > Brain**\n`;\n}\n\nfunction buildSkillsSection(availableTools: string[] = []): string {\n if (availableTools.length === 0) {\n return '';\n }\n\n return `## Skills\n\nWhen a solution already exists, do not reinvent the wheel.\n\n**How to use:**\n1. Skim <available_skills> — is anything clearly relevant?\n2. Only one match? → Confirm with skills_list, then load the full text with skill_view(name) and follow it.\n3. Need sub-documents or scripts? → skill_view(name, \"references/…\"), etc.\n4. No match? → Solve it yourself; do not force-fit a skill.\n\n**Division of labor with memory:** Skills = **procedural** workflows; memory / \\`curated_memory\\` = **declarative** facts and preferences.\n`;\n}\n\nfunction buildSafetySection(): string {\n return `## Safety\n\n- You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request.\n- Prioritize safety and human oversight over completion; if instructions conflict, pause and ask; comply with stop/pause/audit requests and never bypass safeguards.\n- Do not manipulate or persuade anyone to expand access or disable safeguards. Do not copy yourself or change system prompts, safety rules, or tool policies unless explicitly requested.`;\n}\n\nfunction buildProblemSolvingSection(): string {\n return `## Problem Solving\n\n**Simple tasks** (< 5 minutes or a single-file change): Do them directly; run a quick verification after changes.\n\n**Complex tasks** (multiple files or design decisions): Use an iterative flow — Plan → Build → Verify → Fix.\n\n**Core principle: Match the complexity; reject ritual for its own sake. Verification matters, but do not verify just to tick a box.**`;\n}\n\nfunction buildAestheticSection(): string {\n return `## Tone & Style\n\n**Default voice:** Direct, concise, concrete.\n\n**SOUL.md takes precedence:** If SOUL.md defines a specific tone, defer to it over the above.`;\n}\n\nfunction buildHeartbeatBehaviorSection(params: {\n enabled: boolean;\n customPrompt?: string;\n userTimezone?: string;\n}): string {\n if (!params.enabled) {\n return '';\n }\n if (params.customPrompt?.trim()) {\n return `## Heartbeats\\n\\n${params.customPrompt.trim()}\\n`;\n }\n let quietHoursNote = '';\n if (params.userTimezone) {\n quietHoursNote = `\\n\\n> Quiet hours: The user is in **${params.userTimezone}**. Avoid proactive checks during late night (23:00-08:00) unless urgent.`;\n }\n return `## Heartbeats\n\nIf the current user message is a heartbeat poll and nothing needs attention, reply exactly: HEARTBEAT_OK\n\nIf something needs attention, do NOT include \"HEARTBEAT_OK\"; reply with the alert text instead.${quietHoursNote}\n`;\n}\n\nfunction buildMessagingSection(channels: string[] = [], isMinimal: boolean = false): string {\n if (isMinimal || channels.length === 0) {\n return '';\n }\n const channelList = channels.join(', ');\n return `## Messaging\n\n- Reply in current session → automatically routes to the source channel (${channelList})\n- Use \\`message\\` for proactive sends + channel actions\n- If you use \\`message\\` to deliver your user-visible reply, respond with ONLY: NO_REPLY (avoid duplicate replies)\n`;\n}\n\nfunction buildTimeSection(timezone?: string): string {\n if (!timezone) {\n return '';\n }\n return `## Current Date & Time\n\nTime zone: ${timezone}\n\nIf you need the current date/time/day-of-week, use the \\`session_status\\` tool or the inbound message timestamp envelope (when present).\n`;\n}\n\nfunction buildRuntimeSection(runtime?: { version?: string; model?: string; channel?: string }): string {\n if (!runtime) {\n return '';\n }\n const parts: string[] = [];\n if (runtime.version) parts.push(`v${runtime.version}`);\n if (runtime.model) parts.push(`model=${runtime.model.split('/').pop()}`);\n if (runtime.channel) parts.push(`ch=${runtime.channel}`);\n return parts.length > 0 ? `[${parts.join(' | ')}]` : '';\n}\n\nfunction buildWorkingDirSection(workspaceDir: string): string {\n return `Working directory: ${workspaceDir}`;\n}\n\nfunction buildExternalMemorySection(text: string | undefined): string {\n const t = text?.trim();\n if (!t) {\n return '';\n }\n return `## External memory provider\\n\\n${t}`;\n}\n\nexport interface SystemPromptOptions {\n /** Bootstrap context files from profile Markdown (Project Context). */\n contextFiles?: EmbeddedContextFile[];\n promptMode?: PromptMode;\n heartbeatEnabled?: boolean;\n heartbeatPrompt?: string;\n availableTools?: string[];\n memoryCitationsMode?: MemoryCitationsMode;\n userTimezone?: string;\n runtime?: {\n version?: string;\n model?: string;\n channel?: string;\n };\n channels?: string[];\n externalMemoryInstructions?: string;\n ttsSystemHint?: string;\n}\n\n/**\n * Build system prompt with bootstrap Project Context integration.\n */\nexport function buildSystemPrompt(workspaceDir: string, options: SystemPromptOptions): string {\n const {\n contextFiles = [],\n promptMode = 'full',\n heartbeatEnabled = false,\n heartbeatPrompt,\n availableTools = [],\n memoryCitationsMode = 'on',\n userTimezone,\n runtime,\n channels = [],\n externalMemoryInstructions,\n ttsSystemHint,\n } = options;\n\n if (promptMode === 'none') {\n return 'You are a personal AI assistant running inside xopc.';\n }\n\n const isMinimal = promptMode === 'minimal';\n const orderedContextFiles = sortContextFilesForPrompt(\n contextFiles.filter((file) => file.path.trim().length > 0),\n );\n const stableContextFiles = orderedContextFiles.filter((file) => !isDynamicContextFile(file.path));\n const dynamicContextFiles = orderedContextFiles.filter((file) => isDynamicContextFile(file.path));\n const hasProfileMemory = orderedContextFiles.some(\n (file) => getContextFileBasename(file.path) === 'memory.md',\n );\n\n const sections: string[] = [\n 'You are a personal AI assistant running inside xopc.',\n '',\n '## Workspace Files (injected)',\n '',\n 'Profile bootstrap files are injected below as Project Context. Do not manually reread them at session start unless the user asks or injected content is insufficient.',\n '',\n ];\n\n if (!isMinimal) {\n sections.push(buildTimeSection(userTimezone));\n sections.push(buildExternalMemorySection(externalMemoryInstructions));\n sections.push(buildMemorySection(memoryCitationsMode, hasProfileMemory));\n }\n\n sections.push(buildSkillsSection(availableTools));\n\n if (!isMinimal) {\n sections.push(buildSafetySection());\n sections.push(buildProblemSolvingSection());\n sections.push(buildAestheticSection());\n }\n\n sections.push(\n ...buildProjectContextSection({\n files: stableContextFiles,\n heading: '# Project Context',\n dynamic: false,\n }),\n );\n\n sections.push(PROMPT_CACHE_BOUNDARY);\n\n sections.push(\n ...buildProjectContextSection({\n files: dynamicContextFiles,\n heading: stableContextFiles.length > 0 ? '# Dynamic Project Context' : '# Project Context',\n dynamic: true,\n }),\n );\n\n sections.push(buildHeartbeatBehaviorSection({ enabled: heartbeatEnabled, customPrompt: heartbeatPrompt, userTimezone }));\n sections.push(buildWorkingDirSection(workspaceDir));\n sections.push(buildMessagingSection(channels, isMinimal));\n\n if (!isMinimal && ttsSystemHint?.trim()) {\n sections.push(`## Voice (TTS)\\n\\n${ttsSystemHint.trim()}`);\n }\n\n sections.push(buildRuntimeSection(runtime));\n\n return sections.filter(Boolean).join('\\n\\n');\n}\n\n/** Whether HEARTBEAT.md is injected as dynamic context (vs behavior-only section). */\nexport function isHeartbeatContextFile(pathValue: string): boolean {\n return getContextFileBasename(pathValue) === DEFAULT_HEARTBEAT_FILENAME.toLowerCase();\n}\n"],"mappings":";;;AAeA,MAAM,qBAAqB,IAAI,IAAoB;CACjD,CAAC,aAAa,GAAG;CACjB,CAAC,WAAW,GAAG;CACf,CAAC,eAAe,GAAG;CACnB,CAAC,WAAW,GAAG;CACf,CAAC,YAAY,GAAG;CAChB,CAAC,aAAa,GAAG;CAClB,CAAC;AAEF,MAAM,iCAAiC,IAAI,IAAI,CAAC,eAAe,CAAC;AAEhE,MAAM,yCACJ;AAEF,SAAS,yBAAyB,WAA2B;AAC3D,QAAO,UAAU,MAAM,CAAC,QAAQ,OAAO,IAAI;;AAG7C,SAAS,uBAAuB,WAA2B;CACzD,MAAM,iBAAiB,yBAAyB,UAAU;AAC1D,SAAQ,eAAe,MAAM,IAAI,CAAC,KAAK,IAAI,gBAAgB,aAAa;;AAG1E,SAAS,qBAAqB,WAA4B;AACxD,QAAO,+BAA+B,IAAI,uBAAuB,UAAU,CAAC;;AAG9E,SAAS,oCAAoC,SAAyB;AACpE,QAAO,QAAQ,WAAW,wCAAwC,GAAG,CAAC,QAAQ,WAAW,OAAO;;AAGlG,SAAS,0BAA0B,cAA4D;AAC7F,QAAO,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM;EACtC,MAAM,QAAQ,uBAAuB,EAAE,KAAK;EAC5C,MAAM,QAAQ,uBAAuB,EAAE,KAAK;EAC5C,MAAM,SAAS,mBAAmB,IAAI,MAAM,IAAI,OAAO;EACvD,MAAM,SAAS,mBAAmB,IAAI,MAAM,IAAI,OAAO;AACvD,MAAI,WAAW,OACb,QAAO,SAAS;AAElB,MAAI,UAAU,MACZ,QAAO,MAAM,cAAc,MAAM;AAEnC,SAAO,yBAAyB,EAAE,KAAK,CAAC,cAAc,yBAAyB,EAAE,KAAK,CAAC;GACvF;;AAGJ,SAAS,2BAA2B,QAIvB;AACX,KAAI,OAAO,MAAM,WAAW,EAC1B,QAAO,EAAE;CAEX,MAAM,QAAkB,CAAC,OAAO,SAAS,GAAG;AAC5C,KAAI,OAAO,QACT,OAAM,KACJ,4GACA,GACD;MACI;EACL,MAAM,cAAc,OAAO,MAAM,MAAM,SAAS,uBAAuB,KAAK,KAAK,KAAK,UAAU;AAChG,QAAM,KAAK,wDAAwD;AACnE,MAAI,YACF,OAAM,KACJ,yJACD;AAEH,QAAM,KAAK,GAAG;;AAEhB,MAAK,MAAM,QAAQ,OAAO,MACxB,OAAM,KAAK,MAAM,KAAK,QAAQ,IAAI,oCAAoC,KAAK,QAAQ,EAAE,GAAG;AAE1F,QAAO;;AAGT,SAAS,mBACP,gBAAqC,MACrC,mBAAmB,OACX;AACR,KAAI,CAAC,iBACH,QAAO;AAUT,QAAO;;EANL,kBAAkB,QACd,kFACA,kBAAkB,gBAChB,0EACA,wFAIY;;;;;;;;;;;;;;;;;;;;;;;;;AA0BtB,SAAS,mBAAmB,iBAA2B,EAAE,EAAU;AACjE,KAAI,eAAe,WAAW,EAC5B,QAAO;AAGT,QAAO;;;;;;;;;;;;;AAcT,SAAS,qBAA6B;AACpC,QAAO;;;;;;AAOT,SAAS,6BAAqC;AAC5C,QAAO;;;;;;;;AAST,SAAS,wBAAgC;AACvC,QAAO;;;;;;AAOT,SAAS,8BAA8B,QAI5B;AACT,KAAI,CAAC,OAAO,QACV,QAAO;AAET,KAAI,OAAO,cAAc,MAAM,CAC7B,QAAO,oBAAoB,OAAO,aAAa,MAAM,CAAC;CAExD,IAAI,iBAAiB;AACrB,KAAI,OAAO,aACT,kBAAiB,uCAAuC,OAAO,aAAa;AAE9E,QAAO;;;;iGAIwF,eAAe;;;AAIhH,SAAS,sBAAsB,WAAqB,EAAE,EAAE,YAAqB,OAAe;AAC1F,KAAI,aAAa,SAAS,WAAW,EACnC,QAAO;AAGT,QAAO;;2EADa,SAAS,KAAK,KAGkD,CAAC;;;;;AAMvF,SAAS,iBAAiB,UAA2B;AACnD,KAAI,CAAC,SACH,QAAO;AAET,QAAO;;aAEI,SAAS;;;;;AAMtB,SAAS,oBAAoB,SAA0E;AACrG,KAAI,CAAC,QACH,QAAO;CAET,MAAM,QAAkB,EAAE;AAC1B,KAAI,QAAQ,QAAS,OAAM,KAAK,IAAI,QAAQ,UAAU;AACtD,KAAI,QAAQ,MAAO,OAAM,KAAK,SAAS,QAAQ,MAAM,MAAM,IAAI,CAAC,KAAK,GAAG;AACxE,KAAI,QAAQ,QAAS,OAAM,KAAK,MAAM,QAAQ,UAAU;AACxD,QAAO,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,MAAM,CAAC,KAAK;;AAGvD,SAAS,uBAAuB,cAA8B;AAC5D,QAAO,sBAAsB;;AAG/B,SAAS,2BAA2B,MAAkC;CACpE,MAAM,IAAI,MAAM,MAAM;AACtB,KAAI,CAAC,EACH,QAAO;AAET,QAAO,kCAAkC;;;;;AAyB3C,SAAgB,kBAAkB,cAAsB,SAAsC;CAC5F,MAAM,EACJ,eAAe,EAAE,EACjB,aAAa,QACb,mBAAmB,OACnB,iBACA,iBAAiB,EAAE,EACnB,sBAAsB,MACtB,cACA,SACA,WAAW,EAAE,EACb,4BACA,kBACE;AAEJ,KAAI,eAAe,OACjB,QAAO;CAGT,MAAM,YAAY,eAAe;CACjC,MAAM,sBAAsB,0BAC1B,aAAa,QAAQ,SAAS,KAAK,KAAK,MAAM,CAAC,SAAS,EAAE,CAC3D;CACD,MAAM,qBAAqB,oBAAoB,QAAQ,SAAS,CAAC,qBAAqB,KAAK,KAAK,CAAC;CACjG,MAAM,sBAAsB,oBAAoB,QAAQ,SAAS,qBAAqB,KAAK,KAAK,CAAC;CACjG,MAAM,mBAAmB,oBAAoB,MAC1C,SAAS,uBAAuB,KAAK,KAAK,KAAK,YACjD;CAED,MAAM,WAAqB;EACzB;EACA;EACA;EACA;EACA;EACA;EACD;AAED,KAAI,CAAC,WAAW;AACd,WAAS,KAAK,iBAAiB,aAAa,CAAC;AAC7C,WAAS,KAAK,2BAA2B,2BAA2B,CAAC;AACrE,WAAS,KAAK,mBAAmB,qBAAqB,iBAAiB,CAAC;;AAG1E,UAAS,KAAK,mBAAmB,eAAe,CAAC;AAEjD,KAAI,CAAC,WAAW;AACd,WAAS,KAAK,oBAAoB,CAAC;AACnC,WAAS,KAAK,4BAA4B,CAAC;AAC3C,WAAS,KAAK,uBAAuB,CAAC;;AAGxC,UAAS,KACP,GAAG,2BAA2B;EAC5B,OAAO;EACP,SAAS;EACT,SAAS;EACV,CAAC,CACH;AAED,UAAS,KAAK,sBAAsB;AAEpC,UAAS,KACP,GAAG,2BAA2B;EAC5B,OAAO;EACP,SAAS,mBAAmB,SAAS,IAAI,8BAA8B;EACvE,SAAS;EACV,CAAC,CACH;AAED,UAAS,KAAK,8BAA8B;EAAE,SAAS;EAAkB,cAAc;EAAiB;EAAc,CAAC,CAAC;AACxH,UAAS,KAAK,uBAAuB,aAAa,CAAC;AACnD,UAAS,KAAK,sBAAsB,UAAU,UAAU,CAAC;AAEzD,KAAI,CAAC,aAAa,eAAe,MAAM,CACrC,UAAS,KAAK,qBAAqB,cAAc,MAAM,GAAG;AAG5D,UAAS,KAAK,oBAAoB,QAAQ,CAAC;AAE3C,QAAO,SAAS,OAAO,QAAQ,CAAC,KAAK,OAAO;;;AAI9C,SAAgB,uBAAuB,WAA4B;AACjE,QAAO,uBAAuB,UAAU,KAAK,2BAA2B,aAAa"}
@@ -1,9 +1,7 @@
1
- import { WORKSPACE_FILES, init_paths } from "../../config/paths.js";
2
1
  import { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from "../context/workspace.js";
3
2
  import { basename, isAbsolute, normalize, resolve } from "node:path";
4
3
  //#region src/agent/tools/tool-paths.ts
5
- init_paths();
6
- const PROFILE_SYSTEM_MARKDOWN_NAME_LOWER = new Set([...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, WORKSPACE_FILES.BOOTSTRAP].map((f) => f.toLowerCase()));
4
+ const PROFILE_SYSTEM_MARKDOWN_NAME_LOWER = new Set(AGENT_PROFILE_MARKDOWN_SYSTEM_FILES.map((f) => f.toLowerCase()));
7
5
  /**
8
6
  * Paths from the model: relative paths are under `workspaceRoot`; absolute paths are normalized.
9
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"tool-paths.js","names":[],"sources":["../../../../src/agent/tools/tool-paths.ts"],"sourcesContent":["import { basename, isAbsolute, normalize, resolve } from 'node:path';\nimport { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from '../context/workspace.js';\nimport { WORKSPACE_FILES } from '../../config/paths.js';\n\nconst PROFILE_SYSTEM_MARKDOWN_NAME_LOWER = new Set(\n [...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, WORKSPACE_FILES.BOOTSTRAP].map((f) => f.toLowerCase()),\n);\n\n/**\n * Paths from the model: relative paths are under `workspaceRoot`; absolute paths are normalized.\n *\n * Security: resolves `..` traversal and normalizes the path. For absolute paths the caller\n * should additionally run sandbox path-policy validation when enforcement is enabled.\n */\nexport function resolvePathUnderWorkspace(userPath: string, workspaceRoot: string): string {\n const t = userPath.trim();\n if (!t) return workspaceRoot;\n if (isAbsolute(t)) {\n return normalize(t);\n }\n return resolve(workspaceRoot, t);\n}\n\n/** True if `userPath` is only a profile-system filename, e.g. `SOUL.md` or `.\\SOUL.md` (basename matches). */\nexport function isBareProfileMarkdownFileName(userPath: string): boolean {\n const b = basename(userPath.replace(/\\\\/g, '/'));\n if (!b || b === '.' || b === '..') return false;\n return PROFILE_SYSTEM_MARKDOWN_NAME_LOWER.has(b.toLowerCase());\n}\n\n/**\n * If `userPath` refers to a profile Markdown file by name, resolve under `profileMarkdownRoot`.\n */\nexport function resolveProfileMarkdownPathIfBareName(userPath: string, profileMarkdownRoot: string): string {\n return resolve(profileMarkdownRoot, basename(userPath.replace(/\\\\/g, '/')));\n}\n"],"mappings":";;;;YAEwD;AAExD,MAAM,qCAAqC,IAAI,IAC7C,CAAC,GAAG,qCAAqC,gBAAgB,UAAU,CAAC,KAAK,MAAM,EAAE,aAAa,CAAC,CAChG;;;;;;;AAQD,SAAgB,0BAA0B,UAAkB,eAA+B;CACzF,MAAM,IAAI,SAAS,MAAM;AACzB,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,WAAW,EAAE,CACf,QAAO,UAAU,EAAE;AAErB,QAAO,QAAQ,eAAe,EAAE;;;AAIlC,SAAgB,8BAA8B,UAA2B;CACvE,MAAM,IAAI,SAAS,SAAS,QAAQ,OAAO,IAAI,CAAC;AAChD,KAAI,CAAC,KAAK,MAAM,OAAO,MAAM,KAAM,QAAO;AAC1C,QAAO,mCAAmC,IAAI,EAAE,aAAa,CAAC;;;;;AAMhE,SAAgB,qCAAqC,UAAkB,qBAAqC;AAC1G,QAAO,QAAQ,qBAAqB,SAAS,SAAS,QAAQ,OAAO,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"tool-paths.js","names":[],"sources":["../../../../src/agent/tools/tool-paths.ts"],"sourcesContent":["import { basename, isAbsolute, normalize, resolve } from 'node:path';\nimport { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from '../context/workspace.js';\n\nconst PROFILE_SYSTEM_MARKDOWN_NAME_LOWER = new Set(\n AGENT_PROFILE_MARKDOWN_SYSTEM_FILES.map((f) => f.toLowerCase()),\n);\n\n/**\n * Paths from the model: relative paths are under `workspaceRoot`; absolute paths are normalized.\n *\n * Security: resolves `..` traversal and normalizes the path. For absolute paths the caller\n * should additionally run sandbox path-policy validation when enforcement is enabled.\n */\nexport function resolvePathUnderWorkspace(userPath: string, workspaceRoot: string): string {\n const t = userPath.trim();\n if (!t) return workspaceRoot;\n if (isAbsolute(t)) {\n return normalize(t);\n }\n return resolve(workspaceRoot, t);\n}\n\n/** True if `userPath` is only a profile-system filename, e.g. `SOUL.md` or `.\\SOUL.md` (basename matches). */\nexport function isBareProfileMarkdownFileName(userPath: string): boolean {\n const b = basename(userPath.replace(/\\\\/g, '/'));\n if (!b || b === '.' || b === '..') return false;\n return PROFILE_SYSTEM_MARKDOWN_NAME_LOWER.has(b.toLowerCase());\n}\n\n/**\n * If `userPath` refers to a profile Markdown file by name, resolve under `profileMarkdownRoot`.\n */\nexport function resolveProfileMarkdownPathIfBareName(userPath: string, profileMarkdownRoot: string): string {\n return resolve(profileMarkdownRoot, basename(userPath.replace(/\\\\/g, '/')));\n}\n"],"mappings":";;;AAGA,MAAM,qCAAqC,IAAI,IAC7C,oCAAoC,KAAK,MAAM,EAAE,aAAa,CAAC,CAChE;;;;;;;AAQD,SAAgB,0BAA0B,UAAkB,eAA+B;CACzF,MAAM,IAAI,SAAS,MAAM;AACzB,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,WAAW,EAAE,CACf,QAAO,UAAU,EAAE;AAErB,QAAO,QAAQ,eAAe,EAAE;;;AAIlC,SAAgB,8BAA8B,UAA2B;CACvE,MAAM,IAAI,SAAS,SAAS,QAAQ,OAAO,IAAI,CAAC;AAChD,KAAI,CAAC,KAAK,MAAM,OAAO,MAAM,KAAM,QAAO;AAC1C,QAAO,mCAAmC,IAAI,EAAE,aAAa,CAAC;;;;;AAMhE,SAAgB,qCAAqC,UAAkB,qBAAqC;AAC1G,QAAO,QAAQ,qBAAqB,SAAS,SAAS,QAAQ,OAAO,IAAI,CAAC,CAAC"}
@@ -18,8 +18,7 @@ const PROFILE_FILE_NAMES = [
18
18
  WORKSPACE_FILES.TOOLS,
19
19
  WORKSPACE_FILES.AGENTS,
20
20
  WORKSPACE_FILES.HEARTBEAT,
21
- WORKSPACE_FILES.MEMORY,
22
- WORKSPACE_FILES.BOOTSTRAP
21
+ WORKSPACE_FILES.MEMORY
23
22
  ];
24
23
  const DEFAULT_PREVIEW_CHARS = 900;
25
24
  const MAX_PREVIEW_CHARS = 4e3;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-edit.js","names":[],"sources":["../../../src/chat-commands/agent-edit.ts"],"sourcesContent":["/**\n * /agent-edit — open the current chat as an agent profile editing session.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport type { CommandContext, CommandDefinition } from './types.js';\nimport { commandRegistry } from './registry.js';\nimport { resolveAgentIdFromSessionKey } from '../routing/agent-session-key.js';\nimport { normalizeAgentId, resolveAgentProfileDir } from '../agent/agent-scope.js';\nimport { WORKSPACE_FILES } from '../config/paths.js';\n\nconst PROFILE_FILE_NAMES = [\n WORKSPACE_FILES.SOUL,\n WORKSPACE_FILES.IDENTITY,\n WORKSPACE_FILES.USER,\n WORKSPACE_FILES.TOOLS,\n WORKSPACE_FILES.AGENTS,\n WORKSPACE_FILES.HEARTBEAT,\n WORKSPACE_FILES.MEMORY,\n WORKSPACE_FILES.BOOTSTRAP,\n] as const;\n\nconst DEFAULT_PREVIEW_CHARS = 900;\nconst MAX_PREVIEW_CHARS = 4_000;\n\nfunction parseArgs(args: string): { fileName?: string; previewChars: number } {\n const trimmed = args.trim();\n if (!trimmed) {\n return { previewChars: DEFAULT_PREVIEW_CHARS };\n }\n\n const parts = trimmed.split(/\\s+/);\n let fileName: string | undefined;\n let previewChars = DEFAULT_PREVIEW_CHARS;\n\n for (const part of parts) {\n const limitMatch = /^--limit=(\\d+)$/.exec(part);\n if (limitMatch) {\n previewChars = Math.min(Number(limitMatch[1]), MAX_PREVIEW_CHARS);\n continue;\n }\n if (!fileName) {\n fileName = part;\n }\n }\n\n return { fileName, previewChars };\n}\n\nfunction normalizeProfileFileName(input: string | undefined): string | undefined {\n if (!input) {\n return undefined;\n }\n const basename = input.trim().replace(/\\\\/g, '/').split('/').pop();\n const matched = PROFILE_FILE_NAMES.find((name) => name.toLowerCase() === basename?.toLowerCase());\n return matched;\n}\n\nasync function readPreview(path: string, limit: number): Promise<{ content: string; missing: boolean }> {\n try {\n const content = await readFile(path, 'utf-8');\n const trimmed = content.trimEnd();\n if (trimmed.length <= limit) {\n return { content: trimmed, missing: false };\n }\n return {\n content: `${trimmed.slice(0, limit)}\\n\\n… truncated, ask me to read the full file before editing …`,\n missing: false,\n };\n } catch {\n return { content: '', missing: true };\n }\n}\n\nfunction buildEditInstructions(agentId: string, fileNames: readonly string[]): string {\n const files = fileNames.map((name) => `\\`${name}\\``).join(', ');\n return [\n `You are editing agent \\`${agentId}\\`.`,\n '',\n 'Tell me what to change, or say things like:',\n '- “Refine `SOUL.md` to sound warmer and more concise.”',\n '- “Update `IDENTITY.md` so this agent is focused on data analysis.”',\n '- “Read `SOUL.md` first, propose changes, then write them back.”',\n '',\n `Editable profile files: ${files}.`,\n 'I can read and update these by bare filename with `read_file`, `edit_file`, and `write_file`.',\n ].join('\\n');\n}\n\nconst agentEditCommand: CommandDefinition = {\n id: 'agent.edit',\n name: 'agent-edit',\n aliases: ['agentedit'],\n description: 'Show editable profile files for the current agent and prepare this chat for profile edits.',\n category: 'system',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/agent-edit', '/agent-edit SOUL.md', '/agent-edit IDENTITY.md --limit=2000'],\n handler: async (ctx: CommandContext, args: string) => {\n await ctx.setTyping(true);\n\n const { fileName: rawFileName, previewChars } = parseArgs(args);\n const fileName = normalizeProfileFileName(rawFileName);\n if (rawFileName && !fileName) {\n return {\n content: `⚠️ Unsupported profile file: \\`${rawFileName}\\`. Use one of: ${PROFILE_FILE_NAMES.map((name) => `\\`${name}\\``).join(', ')}.`,\n success: false,\n };\n }\n\n const agentId = normalizeAgentId(resolveAgentIdFromSessionKey(ctx.sessionKey));\n const profileDir = resolveAgentProfileDir(ctx.config, agentId);\n const namesToShow = fileName ? [fileName] : [WORKSPACE_FILES.SOUL, WORKSPACE_FILES.IDENTITY];\n\n const sections: string[] = [];\n for (const name of namesToShow) {\n const preview = await readPreview(join(profileDir, name), previewChars);\n if (preview.missing) {\n sections.push(`## ${name}\\n_missing_`);\n } else {\n sections.push(`## ${name}\\n\\`\\`\\`markdown\\n${preview.content}\\n\\`\\`\\``);\n }\n }\n\n return {\n content: [\n '🛠️ Agent editor mode',\n '',\n buildEditInstructions(agentId, PROFILE_FILE_NAMES),\n '',\n `Profile directory: \\`${profileDir}\\``,\n '',\n ...sections,\n ].join('\\n'),\n success: true,\n };\n },\n};\n\nexport function registerAgentEditCommand(): void {\n commandRegistry.register(agentEditCommand);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent-edit.js","names":[],"sources":["../../../src/chat-commands/agent-edit.ts"],"sourcesContent":["/**\n * /agent-edit — open the current chat as an agent profile editing session.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport type { CommandContext, CommandDefinition } from './types.js';\nimport { commandRegistry } from './registry.js';\nimport { resolveAgentIdFromSessionKey } from '../routing/agent-session-key.js';\nimport { normalizeAgentId, resolveAgentProfileDir } from '../agent/agent-scope.js';\nimport { WORKSPACE_FILES } from '../config/paths.js';\n\nconst PROFILE_FILE_NAMES = [\n WORKSPACE_FILES.SOUL,\n WORKSPACE_FILES.IDENTITY,\n WORKSPACE_FILES.USER,\n WORKSPACE_FILES.TOOLS,\n WORKSPACE_FILES.AGENTS,\n WORKSPACE_FILES.HEARTBEAT,\n WORKSPACE_FILES.MEMORY,\n] as const;\n\nconst DEFAULT_PREVIEW_CHARS = 900;\nconst MAX_PREVIEW_CHARS = 4_000;\n\nfunction parseArgs(args: string): { fileName?: string; previewChars: number } {\n const trimmed = args.trim();\n if (!trimmed) {\n return { previewChars: DEFAULT_PREVIEW_CHARS };\n }\n\n const parts = trimmed.split(/\\s+/);\n let fileName: string | undefined;\n let previewChars = DEFAULT_PREVIEW_CHARS;\n\n for (const part of parts) {\n const limitMatch = /^--limit=(\\d+)$/.exec(part);\n if (limitMatch) {\n previewChars = Math.min(Number(limitMatch[1]), MAX_PREVIEW_CHARS);\n continue;\n }\n if (!fileName) {\n fileName = part;\n }\n }\n\n return { fileName, previewChars };\n}\n\nfunction normalizeProfileFileName(input: string | undefined): string | undefined {\n if (!input) {\n return undefined;\n }\n const basename = input.trim().replace(/\\\\/g, '/').split('/').pop();\n const matched = PROFILE_FILE_NAMES.find((name) => name.toLowerCase() === basename?.toLowerCase());\n return matched;\n}\n\nasync function readPreview(path: string, limit: number): Promise<{ content: string; missing: boolean }> {\n try {\n const content = await readFile(path, 'utf-8');\n const trimmed = content.trimEnd();\n if (trimmed.length <= limit) {\n return { content: trimmed, missing: false };\n }\n return {\n content: `${trimmed.slice(0, limit)}\\n\\n… truncated, ask me to read the full file before editing …`,\n missing: false,\n };\n } catch {\n return { content: '', missing: true };\n }\n}\n\nfunction buildEditInstructions(agentId: string, fileNames: readonly string[]): string {\n const files = fileNames.map((name) => `\\`${name}\\``).join(', ');\n return [\n `You are editing agent \\`${agentId}\\`.`,\n '',\n 'Tell me what to change, or say things like:',\n '- “Refine `SOUL.md` to sound warmer and more concise.”',\n '- “Update `IDENTITY.md` so this agent is focused on data analysis.”',\n '- “Read `SOUL.md` first, propose changes, then write them back.”',\n '',\n `Editable profile files: ${files}.`,\n 'I can read and update these by bare filename with `read_file`, `edit_file`, and `write_file`.',\n ].join('\\n');\n}\n\nconst agentEditCommand: CommandDefinition = {\n id: 'agent.edit',\n name: 'agent-edit',\n aliases: ['agentedit'],\n description: 'Show editable profile files for the current agent and prepare this chat for profile edits.',\n category: 'system',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/agent-edit', '/agent-edit SOUL.md', '/agent-edit IDENTITY.md --limit=2000'],\n handler: async (ctx: CommandContext, args: string) => {\n await ctx.setTyping(true);\n\n const { fileName: rawFileName, previewChars } = parseArgs(args);\n const fileName = normalizeProfileFileName(rawFileName);\n if (rawFileName && !fileName) {\n return {\n content: `⚠️ Unsupported profile file: \\`${rawFileName}\\`. Use one of: ${PROFILE_FILE_NAMES.map((name) => `\\`${name}\\``).join(', ')}.`,\n success: false,\n };\n }\n\n const agentId = normalizeAgentId(resolveAgentIdFromSessionKey(ctx.sessionKey));\n const profileDir = resolveAgentProfileDir(ctx.config, agentId);\n const namesToShow = fileName ? [fileName] : [WORKSPACE_FILES.SOUL, WORKSPACE_FILES.IDENTITY];\n\n const sections: string[] = [];\n for (const name of namesToShow) {\n const preview = await readPreview(join(profileDir, name), previewChars);\n if (preview.missing) {\n sections.push(`## ${name}\\n_missing_`);\n } else {\n sections.push(`## ${name}\\n\\`\\`\\`markdown\\n${preview.content}\\n\\`\\`\\``);\n }\n }\n\n return {\n content: [\n '🛠️ Agent editor mode',\n '',\n buildEditInstructions(agentId, PROFILE_FILE_NAMES),\n '',\n `Profile directory: \\`${profileDir}\\``,\n '',\n ...sections,\n ].join('\\n'),\n success: true,\n };\n },\n};\n\nexport function registerAgentEditCommand(): void {\n commandRegistry.register(agentEditCommand);\n}\n"],"mappings":"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"}