@zhin.js/agent 1.1.38 → 1.1.40

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 (120) hide show
  1. package/lib/assistant/job-engine.d.ts +5 -0
  2. package/lib/assistant/job-engine.js +20 -1
  3. package/lib/assistant/job-engine.js.map +1 -1
  4. package/lib/assistant/job-storage.d.ts +238 -0
  5. package/lib/assistant/job-storage.js +143 -0
  6. package/lib/assistant/job-storage.js.map +1 -0
  7. package/lib/assistant/job-store.d.ts +7 -0
  8. package/lib/assistant/job-store.js +41 -3
  9. package/lib/assistant/job-store.js.map +1 -1
  10. package/lib/config/perception-config.d.ts +9 -0
  11. package/lib/config/perception-config.js +27 -0
  12. package/lib/config/perception-config.js.map +1 -1
  13. package/lib/context/context-system.d.ts +1 -0
  14. package/lib/context/context-system.js +8 -8
  15. package/lib/context/context-system.js.map +1 -1
  16. package/lib/context/im-turn-context-adapter.js +2 -0
  17. package/lib/context/im-turn-context-adapter.js.map +1 -1
  18. package/lib/context/index.d.ts +1 -1
  19. package/lib/context/index.js +1 -1
  20. package/lib/context/index.js.map +1 -1
  21. package/lib/context/skill-context.d.ts +3 -0
  22. package/lib/context/skill-context.js +6 -0
  23. package/lib/context/skill-context.js.map +1 -0
  24. package/lib/context/turn-envelope.d.ts +7 -1
  25. package/lib/context/turn-envelope.js +30 -7
  26. package/lib/context/turn-envelope.js.map +1 -1
  27. package/lib/context/turn-user-message.d.ts +3 -1
  28. package/lib/context/turn-user-message.js +14 -1
  29. package/lib/context/turn-user-message.js.map +1 -1
  30. package/lib/core/agent-core-run.d.ts +1 -0
  31. package/lib/core/agent-core-run.js +68 -34
  32. package/lib/core/agent-core-run.js.map +1 -1
  33. package/lib/core/structured-reply-recovery.d.ts +13 -0
  34. package/lib/core/structured-reply-recovery.js +41 -5
  35. package/lib/core/structured-reply-recovery.js.map +1 -1
  36. package/lib/core/turn-completion-usage.d.ts +2 -1
  37. package/lib/core/turn-completion-usage.js +14 -5
  38. package/lib/core/turn-completion-usage.js.map +1 -1
  39. package/lib/core/turn-event-mapper.d.ts +1 -1
  40. package/lib/core/turn-event-mapper.js +8 -5
  41. package/lib/core/turn-event-mapper.js.map +1 -1
  42. package/lib/event/turn-event.d.ts +7 -1
  43. package/lib/event/turn-event.js.map +1 -1
  44. package/lib/index.d.ts +4 -1
  45. package/lib/index.js +2 -0
  46. package/lib/index.js.map +1 -1
  47. package/lib/media/perception.d.ts +3 -4
  48. package/lib/media/perception.js +59 -37
  49. package/lib/media/perception.js.map +1 -1
  50. package/lib/memory/compaction-runtime.d.ts +3 -2
  51. package/lib/memory/compaction-runtime.js +22 -14
  52. package/lib/memory/compaction-runtime.js.map +1 -1
  53. package/lib/outbound/agent-output.js +2 -0
  54. package/lib/outbound/agent-output.js.map +1 -1
  55. package/lib/outbound/chat-failure.d.ts +27 -0
  56. package/lib/outbound/chat-failure.js +53 -0
  57. package/lib/outbound/chat-failure.js.map +1 -0
  58. package/lib/outbound/structured-reply.d.ts +7 -0
  59. package/lib/outbound/structured-reply.js +72 -16
  60. package/lib/outbound/structured-reply.js.map +1 -1
  61. package/lib/plugin-runtime/agent-runtime.js +49 -8
  62. package/lib/plugin-runtime/agent-runtime.js.map +1 -1
  63. package/lib/plugin-runtime/ai-completion.d.ts +4 -3
  64. package/lib/plugin-runtime/ai-completion.js +64 -10
  65. package/lib/plugin-runtime/ai-completion.js.map +1 -1
  66. package/lib/plugin-runtime/capability-ingress.js +3 -1
  67. package/lib/plugin-runtime/capability-ingress.js.map +1 -1
  68. package/lib/plugin-runtime/deferred-capability-plan.d.ts +4 -1
  69. package/lib/plugin-runtime/deferred-capability-plan.js +25 -9
  70. package/lib/plugin-runtime/deferred-capability-plan.js.map +1 -1
  71. package/lib/plugin-runtime/full-agent-turn-engine.js +1 -0
  72. package/lib/plugin-runtime/full-agent-turn-engine.js.map +1 -1
  73. package/lib/plugin-runtime/index.d.ts +2 -0
  74. package/lib/plugin-runtime/index.js +2 -0
  75. package/lib/plugin-runtime/index.js.map +1 -1
  76. package/lib/prompt/assembly.js +0 -9
  77. package/lib/prompt/assembly.js.map +1 -1
  78. package/lib/prompt/git-context.js +1 -1
  79. package/lib/prompt/git-context.js.map +1 -1
  80. package/lib/prompt/rules/chat-rules.js +8 -9
  81. package/lib/prompt/rules/chat-rules.js.map +1 -1
  82. package/lib/prompt/rules/critical-rules.js +10 -10
  83. package/lib/prompt/rules/critical-rules.js.map +1 -1
  84. package/lib/prompt/system-prompt.js +4 -5
  85. package/lib/prompt/system-prompt.js.map +1 -1
  86. package/lib/security/file-role-policy.js +6 -6
  87. package/lib/security/file-role-policy.js.map +1 -1
  88. package/lib/security/policy-facade.js +1 -1
  89. package/lib/security/policy-facade.js.map +1 -1
  90. package/lib/security/usage-quota.d.ts +4 -0
  91. package/lib/security/usage-quota.js +23 -2
  92. package/lib/security/usage-quota.js.map +1 -1
  93. package/lib/session/approval-port.d.ts +2 -0
  94. package/lib/session/approval-port.js.map +1 -1
  95. package/lib/session/approval-review-agent.d.ts +1 -1
  96. package/lib/session/approval-review-agent.js +19 -4
  97. package/lib/session/approval-review-agent.js.map +1 -1
  98. package/lib/session/turn-ingress-session.js +7 -1
  99. package/lib/session/turn-ingress-session.js.map +1 -1
  100. package/lib/tool/tool-approval-gate.js +4 -2
  101. package/lib/tool/tool-approval-gate.js.map +1 -1
  102. package/lib/tool/turn-tool-runtime.js +24 -5
  103. package/lib/tool/turn-tool-runtime.js.map +1 -1
  104. package/lib/tool-catalog/deferred-turn-controller.js +1 -4
  105. package/lib/tool-catalog/deferred-turn-controller.js.map +1 -1
  106. package/lib/trace/context-budget.d.ts +4 -3
  107. package/lib/trace/context-budget.js +28 -8
  108. package/lib/trace/context-budget.js.map +1 -1
  109. package/lib/trace/turn-trace-format.js +4 -1
  110. package/lib/trace/turn-trace-format.js.map +1 -1
  111. package/lib/trace/turn-trace.d.ts +6 -0
  112. package/lib/trace/turn-trace.js +8 -2
  113. package/lib/trace/turn-trace.js.map +1 -1
  114. package/lib/trace/turn-usage-log.d.ts +14 -0
  115. package/lib/trace/turn-usage-log.js +83 -0
  116. package/lib/trace/turn-usage-log.js.map +1 -0
  117. package/lib/usage/usage-store.d.ts +199 -0
  118. package/lib/usage/usage-store.js +121 -0
  119. package/lib/usage/usage-store.js.map +1 -0
  120. package/package.json +13 -13
@@ -1 +1 @@
1
- {"version":3,"file":"turn-envelope.js","sourceRoot":"","sources":["../../src/context/turn-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGlF,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAqBlD,MAAM,UAAU,wBAAwB,CAAC,IAAqB;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,qBAAqB,MAAM,CAAC,QAAQ,eAAe,MAAM,CAAC,QAAQ,MAAM,MAAM,CAAC,KAAK,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;IACrH,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,qCAAqC,MAAM,CAAC,SAAS,EAAE,CAAC;IAC3F,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,iCAAiC,MAAM,CAAC,MAAM,EAAE,CAAC;IACnF,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,qCAAqC,MAAM,CAAC,KAAK,EAAE,CAAC;IAC3F,OAAO,qCAAqC,MAAM,CAAC,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAqB;IAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,OAAO,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE;QAClC,CAAC,CAAC,cAAc,SAAS,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,KAAK,EAAE;QACzF,CAAC,CAAC,cAAc,SAAS,CAAC,SAAS,UAAU,KAAK,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAA+B;IACtE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,CAAC;IAC5D,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,KAAK,EAAE,GAAG,CAAC,CAAC;IAEvC,IAAI,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5E,IAAI,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAE9D,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,cAAc,UAAU,CAAC,gBAAgB,IAAI,UAAU,CAAC,aAAa,aAAa,UAAU,CAAC,MAAM,IAAI,iBAAiB,EAAE,CAAC,CAAC;QACzI,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;YAC1C,MAAM,WAAW,GAAG,qBAAqB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAChE,IAAI,WAAW;gBAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAC;YAC5D,MAAM,UAAU,GAAG,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YACzE,IAAI,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/F,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/E,IAAI,KAAK,CAAC,mBAAmB,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;IACpF,IAAI,KAAK,CAAC,eAAe,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,IAAI,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;IAE1E,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;QACvB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,GAAG,kBAAkB,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,gBAAgB,EAAE,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAe,EAAE,QAAmC;IAC7F,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE;QAAE,OAAO,OAAO,CAAC;IACtC,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,OAAO,OAAO,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import { getFileMemoryContext, formatMemoryPathsHint } from '../memory-layers.js';\nimport type { TurnRequest } from '../turn/turn-ingress.js';\n\nexport const TURN_CONTEXT_BEGIN = '[Turn context]';\nexport const TURN_CONTEXT_END = '[/Turn context]';\n\nexport type TurnContextView = Pick<TurnRequest, 'origin' | 'principal' | 'session'>\n & Partial<Pick<TurnRequest, 'policy'>>;\n\nexport interface TurnContextEnvelopeInput {\n turn?: TurnContextView;\n profileSummary?: string;\n toneHint?: string;\n deferredStats?: string;\n activeSkillsContext?: string;\n quoteSystemHint?: string;\n modelLine?: string;\n sdk?: string;\n agentsContext?: string;\n /** Rendered \"already told the user\" ledger (#730). */\n toldLedger?: string;\n /** BM25-recalled semantic memory for this message (#734). */\n relevantMemory?: string;\n}\n\nexport function formatSessionContextLine(turn: TurnContextView): string {\n const origin = turn.origin;\n if (origin.kind === 'im') {\n return `Session: platform:${origin.platform} | endpoint:${origin.endpoint} | ${origin.scope}_id:${origin.sceneId}`;\n }\n if (origin.kind === 'http') return `Session: origin:http | session_id:${origin.sessionId}`;\n if (origin.kind === 'a2a') return `Session: origin:a2a | task_id:${origin.taskId}`;\n if (origin.kind === 'schedule') return `Session: origin:schedule | job_id:${origin.jobId}`;\n return `Session: origin:internal | source:${origin.source}`;\n}\n\nexport function formatSenderContextLine(turn: TurnContextView): string {\n const { principal } = turn;\n const roles = principal.roles.length > 0 ? principal.roles.join(',') : 'user';\n return principal.displayName?.trim()\n ? `Sender: id=${principal.subjectId} name=${principal.displayName.trim()} roles=${roles}`\n : `Sender: id=${principal.subjectId} roles=${roles}`;\n}\n\nexport function buildTurnContextEnvelope(input: TurnContextEnvelopeInput): string | null {\n const lines: string[] = [];\n const now = new Date();\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n const timeStr = now.toLocaleString('zh-CN', { timeZone: tz });\n lines.push(`Time: ${timeStr} (${tz})`);\n\n if (input.modelLine?.trim()) lines.push(`Model: ${input.modelLine.trim()}`);\n if (input.sdk?.trim()) lines.push(`Sdk: ${input.sdk.trim()}`);\n\n if (input.turn) {\n lines.push(formatSessionContextLine(input.turn));\n lines.push(formatSenderContextLine(input.turn));\n if (input.turn.policy?.filesystem) {\n const filesystem = input.turn.policy.filesystem;\n lines.push(`Workspace: ${filesystem.workingDirectory ?? filesystem.workspaceRoot} | access:${filesystem.access ?? 'workspace-write'}`);\n }\n if (input.turn.origin.kind === 'im') {\n const platform = input.turn.origin.platform;\n const sessionKey = input.turn.session.key;\n const memoryPaths = formatMemoryPathsHint(platform, sessionKey);\n if (memoryPaths) lines.push(`Memory paths: ${memoryPaths}`);\n const fileMemory = getFileMemoryContext(undefined, platform, sessionKey);\n if (fileMemory?.trim()) {\n lines.push('Memory snapshot:');\n lines.push(fileMemory.trim());\n }\n }\n }\n\n if (input.deferredStats?.trim()) lines.push(`Deferred catalog: ${input.deferredStats.trim()}`);\n if (input.profileSummary?.trim()) lines.push(input.profileSummary.trim());\n if (input.toneHint?.trim()) lines.push(`[Tone hint] ${input.toneHint.trim()}`);\n if (input.activeSkillsContext?.trim()) lines.push(input.activeSkillsContext.trim());\n if (input.quoteSystemHint?.trim()) lines.push(input.quoteSystemHint.trim());\n if (input.agentsContext?.trim()) lines.push(input.agentsContext.trim());\n if (input.toldLedger?.trim()) lines.push(input.toldLedger.trim());\n if (input.relevantMemory?.trim()) lines.push(input.relevantMemory.trim());\n\n return lines.length === 0\n ? null\n : `${TURN_CONTEXT_BEGIN}\\n${lines.join('\\n')}\\n${TURN_CONTEXT_END}`;\n}\n\nexport function prependTurnContextEnvelope(content: string, envelope: string | null | undefined): string {\n if (!envelope?.trim()) return content;\n return `${envelope.trim()}\\n\\n${content}`;\n}\n"]}
1
+ {"version":3,"file":"turn-envelope.js","sourceRoot":"","sources":["../../src/context/turn-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAyB,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGlF,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAKlD,iFAAiF;AACjF,MAAM,CAAC,MAAM,+BAA+B,GAAG,gBAAgB,CAAC;AAEhE,MAAM,uBAAuB,GAAG,4FAA4F;MACxH,uHAAuH,CAAC;AAoB5H,MAAM,UAAU,wBAAwB,CAAC,IAAqB;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,qBAAqB,MAAM,CAAC,QAAQ,eAAe,MAAM,CAAC,QAAQ,MAAM,MAAM,CAAC,KAAK,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;IACrH,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,qCAAqC,MAAM,CAAC,SAAS,EAAE,CAAC;IAC3F,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,iCAAiC,MAAM,CAAC,MAAM,EAAE,CAAC;IACnF,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,qCAAqC,MAAM,CAAC,KAAK,EAAE,CAAC;IAC3F,OAAO,qCAAqC,MAAM,CAAC,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAqB;IAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC3B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,OAAO,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE;QAClC,CAAC,CAAC,cAAc,SAAS,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,KAAK,EAAE;QACzF,CAAC,CAAC,cAAc,SAAS,CAAC,SAAS,UAAU,KAAK,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAA+B;IACtE,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAA+B;IACpE,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,MAAM,KAAK,GAAa,CAAC,kBAAkB,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,KAAK,CAAC,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,CAAC;IAC5D,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,KAAK,EAAE,GAAG,CAAC,CAAC;IAEvC,IAAI,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5E,IAAI,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9D,IAAI,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAEhE,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,+BAA+B,CAAC,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACrH,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,cAAc,UAAU,CAAC,gBAAgB,IAAI,UAAU,CAAC,aAAa,aAAa,UAAU,CAAC,MAAM,IAAI,iBAAiB,EAAE,CAAC,CAAC;QACzI,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;YAC1C,MAAM,WAAW,GAAG,qBAAqB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAChE,IAAI,WAAW;gBAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAC;YAC5D,MAAM,UAAU,GAAG,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YACzE,IAAI,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/F,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/E,IAAI,KAAK,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;QACpC,KAAK,EAAE,CAAC;QACR,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC1C,CAAC;SAAM,IAAI,KAAK,CAAC,mBAAmB,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3F,IAAI,KAAK,CAAC,eAAe,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,IAAI,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE,EAAE,CAAC;QAChC,KAAK,EAAE,CAAC;QACR,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,iBAAiB,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;IAE1E,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC;IACR,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAe,EAAE,QAAmC;IAC7F,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE;QAAE,OAAO,OAAO,CAAC;IACtC,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,OAAO,OAAO,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import { createPromptContextBlock, type TextContentBlock } from '@zhin.js/ai';\nimport { getFileMemoryContext, formatMemoryPathsHint } from '../memory-layers.js';\nimport type { TurnRequest } from '../turn/turn-ingress.js';\n\nexport const TURN_CONTEXT_BEGIN = '[Turn context]';\nexport const TURN_CONTEXT_END = '[/Turn context]';\n\nexport type TurnContextView = Pick<TurnRequest, 'origin' | 'principal' | 'session'>\n & Partial<Pick<TurnRequest, 'policy' | 'input'>>;\n\n/** Trusted turn metadata key the IM ingress sets for a turn nobody addressed. */\nexport const AMBIENT_ENGAGEMENT_METADATA_KEY = 'zhinEngagement';\n\nconst AMBIENT_ENGAGEMENT_LINE = 'Engagement: ambient. Nobody called you; you are reading the group chat and chose to look. '\n + 'Reply only if a group member like you would naturally chime in here; otherwise return {\"messages\":[]} and stay quiet.';\n\nexport interface TurnContextEnvelopeInput {\n turn?: TurnContextView;\n profileSummary?: string;\n toneHint?: string;\n deferredStats?: string;\n activeSkillsContext?: string;\n activeSkillBlocks?: readonly TextContentBlock[];\n quoteSystemHint?: string;\n modelLine?: string;\n sdk?: string;\n gitStatus?: string | null;\n agentsContext?: string;\n /** Rendered \"already told the user\" ledger (#730). */\n toldLedger?: string;\n /** BM25-recalled semantic memory for this message (#734). */\n relevantMemory?: string;\n}\n\nexport function formatSessionContextLine(turn: TurnContextView): string {\n const origin = turn.origin;\n if (origin.kind === 'im') {\n return `Session: platform:${origin.platform} | endpoint:${origin.endpoint} | ${origin.scope}_id:${origin.sceneId}`;\n }\n if (origin.kind === 'http') return `Session: origin:http | session_id:${origin.sessionId}`;\n if (origin.kind === 'a2a') return `Session: origin:a2a | task_id:${origin.taskId}`;\n if (origin.kind === 'schedule') return `Session: origin:schedule | job_id:${origin.jobId}`;\n return `Session: origin:internal | source:${origin.source}`;\n}\n\nexport function formatSenderContextLine(turn: TurnContextView): string {\n const { principal } = turn;\n const roles = principal.roles.length > 0 ? principal.roles.join(',') : 'user';\n return principal.displayName?.trim()\n ? `Sender: id=${principal.subjectId} name=${principal.displayName.trim()} roles=${roles}`\n : `Sender: id=${principal.subjectId} roles=${roles}`;\n}\n\nexport function buildTurnContextEnvelope(input: TurnContextEnvelopeInput): string | null {\n return buildTurnContextBlocks(input).map(block => block.text).join('\\n');\n}\n\nexport function buildTurnContextBlocks(input: TurnContextEnvelopeInput): TextContentBlock[] {\n const blocks: TextContentBlock[] = [];\n const lines: string[] = [TURN_CONTEXT_BEGIN];\n const flush = () => {\n if (lines.length) blocks.push({ type: 'text', text: lines.splice(0).join('\\n') });\n };\n const now = new Date();\n const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n const timeStr = now.toLocaleString('zh-CN', { timeZone: tz });\n lines.push(`Time: ${timeStr} (${tz})`);\n\n if (input.modelLine?.trim()) lines.push(`Model: ${input.modelLine.trim()}`);\n if (input.sdk?.trim()) lines.push(`Sdk: ${input.sdk.trim()}`);\n if (input.gitStatus?.trim()) lines.push(input.gitStatus.trim());\n\n if (input.turn) {\n lines.push(formatSessionContextLine(input.turn));\n lines.push(formatSenderContextLine(input.turn));\n if (input.turn.input?.metadata?.[AMBIENT_ENGAGEMENT_METADATA_KEY] === 'ambient') lines.push(AMBIENT_ENGAGEMENT_LINE);\n if (input.turn.policy?.filesystem) {\n const filesystem = input.turn.policy.filesystem;\n lines.push(`Workspace: ${filesystem.workingDirectory ?? filesystem.workspaceRoot} | access:${filesystem.access ?? 'workspace-write'}`);\n }\n if (input.turn.origin.kind === 'im') {\n const platform = input.turn.origin.platform;\n const sessionKey = input.turn.session.key;\n const memoryPaths = formatMemoryPathsHint(platform, sessionKey);\n if (memoryPaths) lines.push(`Memory paths: ${memoryPaths}`);\n const fileMemory = getFileMemoryContext(undefined, platform, sessionKey);\n if (fileMemory?.trim()) {\n lines.push('Memory snapshot:');\n lines.push(fileMemory.trim());\n }\n }\n }\n\n if (input.deferredStats?.trim()) lines.push(`Deferred catalog: ${input.deferredStats.trim()}`);\n if (input.profileSummary?.trim()) lines.push(input.profileSummary.trim());\n if (input.toneHint?.trim()) lines.push(`[Tone hint] ${input.toneHint.trim()}`);\n if (input.activeSkillBlocks?.length) {\n flush();\n blocks.push(...input.activeSkillBlocks);\n } else if (input.activeSkillsContext?.trim()) lines.push(input.activeSkillsContext.trim());\n if (input.quoteSystemHint?.trim()) lines.push(input.quoteSystemHint.trim());\n if (input.agentsContext?.trim()) {\n flush();\n blocks.push(createPromptContextBlock('workspace-rules', input.agentsContext.trim()));\n }\n if (input.toldLedger?.trim()) lines.push(input.toldLedger.trim());\n if (input.relevantMemory?.trim()) lines.push(input.relevantMemory.trim());\n\n lines.push(TURN_CONTEXT_END);\n flush();\n return blocks;\n}\n\nexport function prependTurnContextEnvelope(content: string, envelope: string | null | undefined): string {\n if (!envelope?.trim()) return content;\n return `${envelope.trim()}\\n\\n${content}`;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type AgentMessageExtra, type AgentMessage, type UserMessage } from '@zhin.js/ai';
1
+ import { type AgentMessageExtra, type AgentMessage, type UserMessage, type TextContentBlock } from '@zhin.js/ai';
2
2
  import type { Message } from '@zhin.js/core';
3
3
  export declare function buildTurnUserMessages(commMessage: Message, rawContent: string): {
4
4
  rawContent: string;
@@ -7,3 +7,5 @@ export declare function buildTurnUserMessages(commMessage: Message, rawContent:
7
7
  };
8
8
  export declare function applyTurnContextToUserMessages(messages: UserMessage[], envelope: string | null | undefined): UserMessage[];
9
9
  export declare function prependEnvelopeToFirstUserText(messages: AgentMessage[], envelope: string | null | undefined): AgentMessage[];
10
+ /** Keep provenance-bearing guidance separate from the actual participant text. */
11
+ export declare function prependTurnContextBlocks(messages: AgentMessage[], blocks: readonly TextContentBlock[], extra?: AgentMessageExtra): AgentMessage[];
@@ -1,4 +1,4 @@
1
- import { createUserMessage, userMessagePlainText } from '@zhin.js/ai';
1
+ import { createUserMessage, userMessagePlainText, userMessageBody, renderUserMessageForLlm } from '@zhin.js/ai';
2
2
  import { resolveTurnUserMessage } from '../session/session-io.js';
3
3
  import { prependTurnContextEnvelope } from './turn-envelope.js';
4
4
  export function buildTurnUserMessages(commMessage, rawContent) {
@@ -44,4 +44,17 @@ export function prependEnvelopeToFirstUserText(messages, envelope) {
44
44
  nextContent[textIdx] = { type: 'text', text: nextText };
45
45
  return [{ ...first, content: nextContent }, ...messages.slice(1)];
46
46
  }
47
+ /** Keep provenance-bearing guidance separate from the actual participant text. */
48
+ export function prependTurnContextBlocks(messages, blocks, extra) {
49
+ const first = messages[0];
50
+ if (first?.role !== 'user' || !Array.isArray(first.content) || !blocks.length)
51
+ return messages;
52
+ const user = first;
53
+ // Only the first text block carries the ingress presentation. Preserve the
54
+ // other blocks (including provenance) and current media without flattening.
55
+ const content = user.content.map((block, index) => index === 0 && !block.contextRef
56
+ ? { ...block, text: userMessageBody({ ...user, content: [block] }, extra) }
57
+ : block);
58
+ return [renderUserMessageForLlm({ ...user, content: [...blocks, ...content] }, extra), ...messages.slice(1)];
59
+ }
47
60
  //# sourceMappingURL=turn-user-message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"turn-user-message.js","sourceRoot":"","sources":["../../src/context/turn-user-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AgentMessageExtra, type AgentMessage, type UserMessage, createUserMessage, userMessagePlainText } from '@zhin.js/ai';\nimport type { Message } from '@zhin.js/core';\nimport { resolveTurnUserMessage } from '../session/session-io.js';\nimport { prependTurnContextEnvelope } from './turn-envelope.js';\nexport function buildTurnUserMessages(\n commMessage: Message,\n rawContent: string,\n): {\n rawContent: string;\n userMessageExtra?: AgentMessageExtra;\n promptMessages: UserMessage[];\n} {\n const { content, llmMessage } = resolveTurnUserMessage(\n commMessage as import('@zhin.js/core').AgentTurnMessage,\n rawContent,\n );\n return {\n rawContent: content,\n promptMessages: [llmMessage],\n };\n}\n\nexport function applyTurnContextToUserMessages(\n messages: UserMessage[],\n envelope: string | null | undefined,\n): UserMessage[] {\n if (!envelope?.trim() || messages.length === 0) return messages;\n return messages.map((msg, i) => {\n if (i !== 0 || msg.role !== 'user') return msg;\n const text = userMessagePlainText(msg);\n return createUserMessage(\n prependTurnContextEnvelope(text, envelope),\n msg.media,\n msg.timestamp,\n msg.actor,\n msg.cause,\n );\n });\n}\n\nexport function prependEnvelopeToFirstUserText(\n messages: AgentMessage[],\n envelope: string | null | undefined,\n): AgentMessage[] {\n if (!envelope?.trim() || messages.length === 0) return messages;\n const first = messages[0];\n if (!first || first.role !== 'user') return messages;\n\n const content = first.content;\n if (!Array.isArray(content)) return messages;\n\n const textIdx = content.findIndex((b) => b?.type === 'text');\n if (textIdx < 0) {\n return [\n { ...first, content: [{ type: 'text', text: envelope.trim() }, ...content] },\n ...messages.slice(1),\n ];\n }\n\n const block = content[textIdx];\n if (block.type !== 'text') return messages;\n const nextText = prependTurnContextEnvelope(block.text, envelope);\n if (nextText === block.text) return messages;\n\n const nextContent = [...content];\n nextContent[textIdx] = { type: 'text', text: nextText };\n return [{ ...first, content: nextContent }, ...messages.slice(1)];\n}\n"]}
1
+ {"version":3,"file":"turn-user-message.js","sourceRoot":"","sources":["../../src/context/turn-user-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AgentMessageExtra, type AgentMessage, type UserMessage, type TextContentBlock, createUserMessage, userMessagePlainText, userMessageBody, renderUserMessageForLlm } from '@zhin.js/ai';\nimport type { Message } from '@zhin.js/core';\nimport { resolveTurnUserMessage } from '../session/session-io.js';\nimport { prependTurnContextEnvelope } from './turn-envelope.js';\nexport function buildTurnUserMessages(\n commMessage: Message,\n rawContent: string,\n): {\n rawContent: string;\n userMessageExtra?: AgentMessageExtra;\n promptMessages: UserMessage[];\n} {\n const { content, llmMessage } = resolveTurnUserMessage(\n commMessage as import('@zhin.js/core').AgentTurnMessage,\n rawContent,\n );\n return {\n rawContent: content,\n promptMessages: [llmMessage],\n };\n}\n\nexport function applyTurnContextToUserMessages(\n messages: UserMessage[],\n envelope: string | null | undefined,\n): UserMessage[] {\n if (!envelope?.trim() || messages.length === 0) return messages;\n return messages.map((msg, i) => {\n if (i !== 0 || msg.role !== 'user') return msg;\n const text = userMessagePlainText(msg);\n return createUserMessage(\n prependTurnContextEnvelope(text, envelope),\n msg.media,\n msg.timestamp,\n msg.actor,\n msg.cause,\n );\n });\n}\n\nexport function prependEnvelopeToFirstUserText(\n messages: AgentMessage[],\n envelope: string | null | undefined,\n): AgentMessage[] {\n if (!envelope?.trim() || messages.length === 0) return messages;\n const first = messages[0];\n if (!first || first.role !== 'user') return messages;\n\n const content = first.content;\n if (!Array.isArray(content)) return messages;\n\n const textIdx = content.findIndex((b) => b?.type === 'text');\n if (textIdx < 0) {\n return [\n { ...first, content: [{ type: 'text', text: envelope.trim() }, ...content] },\n ...messages.slice(1),\n ];\n }\n\n const block = content[textIdx];\n if (block.type !== 'text') return messages;\n const nextText = prependTurnContextEnvelope(block.text, envelope);\n if (nextText === block.text) return messages;\n\n const nextContent = [...content];\n nextContent[textIdx] = { type: 'text', text: nextText };\n return [{ ...first, content: nextContent }, ...messages.slice(1)];\n}\n\n/** Keep provenance-bearing guidance separate from the actual participant text. */\nexport function prependTurnContextBlocks(messages: AgentMessage[], blocks: readonly TextContentBlock[], extra?: AgentMessageExtra): AgentMessage[] {\n const first = messages[0];\n if (first?.role !== 'user' || !Array.isArray(first.content) || !blocks.length) return messages;\n const user = first as UserMessage;\n // Only the first text block carries the ingress presentation. Preserve the\n // other blocks (including provenance) and current media without flattening.\n const content = user.content.map((block, index) => index === 0 && !block.contextRef\n ? { ...block, text: userMessageBody({ ...user, content: [block] }, extra) }\n : block);\n return [renderUserMessageForLlm({ ...user, content: [...blocks, ...content] }, extra), ...messages.slice(1)];\n}\n"]}
@@ -52,6 +52,7 @@ export interface AgentLoopTurnInput {
52
52
  /** Turn-owned deferred selection authority. Required for deferred loading. */
53
53
  deferredController?: Readonly<{
54
54
  loadedToolNames(): string[];
55
+ modelOutputBlocks?(name: string, args: unknown, text: string): import('@zhin.js/ai').TextContentBlock[] | undefined;
55
56
  /** Optional: lets the loop auto-load a deferred tool the model called directly. */
56
57
  load?(name: string): Promise<boolean>;
57
58
  }>;
@@ -5,7 +5,7 @@ import { decodeStructuredReply, structuredReplyInstruction, structuredReplySchem
5
5
  import { renderAgentOutput } from '../outbound/agent-output.js';
6
6
  import { mergeToolOutboundElements } from '../media/media-tool-bridge.js';
7
7
  import { formatCompact, truncatePreview, getLogger } from '@zhin.js/logger';
8
- import { parseOutput, agentLoop, agentContextFrom, assistantText, createUserMessage, agentToolsToLlmTools, createResilientCompletionPort, isProviderFailureMessage, resolveProviderConfigResilience, resolveProviderResilienceConfig } from '@zhin.js/ai';
8
+ import { createPromptContextBlock, projectPromptContext, estimateContextFixedTokens, parseOutput, agentLoop, agentContextFrom, assistantText, createUserMessage, agentToolsToLlmTools, createResilientCompletionPort, isProviderFailureMessage, resolveProviderConfigResilience, resolveProviderResilienceConfig } from '@zhin.js/ai';
9
9
  import { tokenUsageToLegacy } from './agent-run-shared.js';
10
10
  import { applyExecPolicyToTools } from '../security/exec-policy.js';
11
11
  import { resolveModelHarness } from '../config/model-harness-runtime.js';
@@ -15,7 +15,8 @@ import { planToolRun } from '../tool/runtime.js';
15
15
  import { sanitizeAssistantReply, unwrapJsonStringLayers } from './text-sanitize.js';
16
16
  import { formatToolCallsForUser } from './tool-calls-user-format.js';
17
17
  import { summarizeExhaustedTurn } from './exhausted-turn-reply.js';
18
- import { recoverStructuredReply, STRUCTURED_REPLY_FAILURE } from './structured-reply-recovery.js';
18
+ import { decodeLenientStructuredReply, recoverStructuredReply } from './structured-reply-recovery.js';
19
+ import { chatFailureKindOf, chatFailureLine } from '../outbound/chat-failure.js';
19
20
  import { trackTurnCompletionUsage } from './turn-completion-usage.js';
20
21
  import { shouldSuppressReplyForSpawnDelegation } from './spawn-delegation.js';
21
22
  import { logPhase, tokenUsageLogFields, logAgentLoopIterationEnd } from '../internal/phase-trace.js';
@@ -24,7 +25,7 @@ import { buildLlmToolsForProvider } from '../tool/deferred-resolution.js';
24
25
  import { applyToolToModelOutput } from '../tool/tool-model-output.js';
25
26
  import { resolveAlwaysLoadedSet } from '../tool-catalog/resolve-config.js';
26
27
  import { resolveDeferredApiTools, residentToolNames } from '../tool-catalog/tool-catalog.js';
27
- import { createTurnEventMapperState, mapAgentEventToTurnEvents, turnUsageEvent, } from './turn-event-mapper.js';
28
+ import { createTurnEventMapperState, mapAgentEventToTurnEvents, } from './turn-event-mapper.js';
28
29
  import { TurnJournalCommitError } from '../turn/journal-integrity.js';
29
30
  import { ExecutableToolRegistry } from '../tool/executable-tool-registry.js';
30
31
  import { withoutForbiddenShell } from '../plugin-runtime/native-bash-tool.js';
@@ -33,7 +34,6 @@ import { createMissingToolResolver } from './missing-tool-resolver.js';
33
34
  import { DEFAULT_EXPLORATION_BUDGET, EXPLORATION_GUIDANCE, ExplorationBudget, } from './exploration-budget.js';
34
35
  import { TurnProgressTracker, autoContinueAllowed, buildContinuationInstruction, isProgress, resolveToolBudget, } from './turn-progress.js';
35
36
  import { TurnProgressReporter, summarizeProgress } from './progress-reporter.js';
36
- import { estimatePromptTokens } from '../prompt/estimate-tokens.js';
37
37
  import { noteTurnTrace } from '../trace/turn-trace-registry.js';
38
38
  import { buildContextBudgetReport } from '../trace/context-budget.js';
39
39
  import { renderDeferredToolIndex } from '../skill/capability-catalog.js';
@@ -46,11 +46,14 @@ function normalizeAssistantMessageText(assistant) {
46
46
  }
47
47
  }
48
48
  }
49
- function resolveAssistantReplyText(assistant) {
49
+ function resolveAssistantReplyText(assistant, chat) {
50
50
  normalizeAssistantMessageText(assistant);
51
51
  const text = assistantText(assistant);
52
52
  if (text.trim())
53
53
  return text;
54
+ // In a chat the failure is said like a member would say it; operators read the logs.
55
+ if (assistant.errorMessage && chat)
56
+ return chatFailureLine(chatFailureKindOf(assistant.errorMessage), chat);
54
57
  if (assistant.errorMessage && isProviderFailureMessage(assistant.errorMessage)) {
55
58
  return `${assistant.errorMessage}。`;
56
59
  }
@@ -184,7 +187,7 @@ async function* runAgentLoopVisionTurnOnceRun(input) {
184
187
  modelId,
185
188
  label: 'vision',
186
189
  }),
187
- convertToLlm: (messages) => messages,
190
+ convertToLlm: projectPromptContext,
188
191
  getSteeringMessages: promptHooks?.getSteeringMessages,
189
192
  getFollowUpMessages: promptHooks?.getFollowUpMessages,
190
193
  };
@@ -320,7 +323,7 @@ export async function* runAgentLoopTextTurnRun(input) {
320
323
  const loaded = persistentConversation
321
324
  ? await repo.loadContext(sessionId)
322
325
  : { messages: [] };
323
- const promptMessages = input.initialMessages?.length
326
+ let promptMessages = input.initialMessages?.length
324
327
  ? input.initialMessages
325
328
  : [createUserMessage(input.rawContent)];
326
329
  const directTools = input.toolLoading === 'direct';
@@ -336,6 +339,9 @@ export async function* runAgentLoopTextTurnRun(input) {
336
339
  const messagePreset = configuredSchema === true || configuredSchema === 'segments';
337
340
  const imReply = messagePreset && input.turnContext.origin.kind === 'im'
338
341
  && input.conversationPersistence !== 'none';
342
+ const chatFailure = input.turnContext.origin.kind === 'im'
343
+ ? { master: input.turnContext.principal.roles.includes('master') }
344
+ : undefined;
339
345
  const outputSchema = imReply ? structuredReplySchema(input.replySegments)
340
346
  : messagePreset || !configuredSchema ? undefined : configuredSchema;
341
347
  const turnShellPolicy = input.turnContext.policy?.shell;
@@ -433,8 +439,15 @@ export async function* runAgentLoopTextTurnRun(input) {
433
439
  if (hasTools && !directTools) {
434
440
  const toolIndex = renderDeferredToolIndex(deferredCatalogTools(catalog, agentTools)
435
441
  .map((item) => ({ name: item.name, description: item.brief })));
436
- if (toolIndex)
437
- systemPrompt = `${systemPrompt}\n\n${toolIndex}`;
442
+ if (toolIndex) {
443
+ const block = createPromptContextBlock('deferred-tools', toolIndex);
444
+ const userIndex = promptMessages.map(message => message.role === 'user' && Array.isArray(message.content)).lastIndexOf(true);
445
+ promptMessages = userIndex < 0
446
+ ? [...promptMessages, { ...createUserMessage(''), content: [block] }]
447
+ : promptMessages.map((message, index) => index === userIndex && Array.isArray(message.content)
448
+ ? { ...message, content: [...message.content, block] }
449
+ : message);
450
+ }
438
451
  }
439
452
  if (networkWithheld)
440
453
  systemPrompt = `${systemPrompt}\n\n${NETWORK_DISABLED_PROMPT_NOTE}`;
@@ -447,6 +460,7 @@ export async function* runAgentLoopTextTurnRun(input) {
447
460
  kind: 'context_budget',
448
461
  report: buildContextBudgetReport({
449
462
  systemPrompt,
463
+ outputSchema,
450
464
  sections: promptSections,
451
465
  bootstrapContext: input.promptRuntime?.bootstrapContext,
452
466
  activeSkillsContext: input.promptRuntime?.activeSkillsContext,
@@ -490,8 +504,16 @@ export async function* runAgentLoopTextTurnRun(input) {
490
504
  const contextWindow = llmModel.contextWindow ?? host.config.contextTokens;
491
505
  // System prompt and tool schemas count against the window too, so the
492
506
  // compaction threshold is measured on what is left for messages (#730).
493
- const fixedPromptTokens = () => estimatePromptTokens(systemPrompt)
494
- + llmTools.reduce((sum, tool) => sum + estimatePromptTokens(`${tool.name} ${tool.description}`), 0);
507
+ let budgetTools;
508
+ let fixedBudget;
509
+ const fixedPromptTokens = () => {
510
+ // 本回合系统提示不变;工具渐进披露替换数组后重新计算完整 Schema。
511
+ if (!fixedBudget || budgetTools !== llmTools) {
512
+ budgetTools = llmTools;
513
+ fixedBudget = estimateContextFixedTokens({ systemPrompt, tools: llmTools, model: llmModel, outputSchema });
514
+ }
515
+ return fixedBudget;
516
+ };
495
517
  const loopHooks = input.loopHooks;
496
518
  const completionPort = createTurnCompletionPort({
497
519
  host,
@@ -544,7 +566,7 @@ export async function* runAgentLoopTextTurnRun(input) {
544
566
  }),
545
567
  ...(outputSchema ? { outputSchema } : {}),
546
568
  },
547
- convertToLlm: (messages) => messages,
569
+ convertToLlm: projectPromptContext,
548
570
  transformContext: async (messages, ctxSignal) => persistentConversation ? host.compactionRuntime.transformContext(messages, ctxSignal, {
549
571
  host,
550
572
  sessionId,
@@ -552,7 +574,7 @@ export async function* runAgentLoopTextTurnRun(input) {
552
574
  model: llmModel,
553
575
  compactionConfig: host.config.compaction,
554
576
  contextWindow,
555
- reservedTokens: fixedPromptTokens(),
577
+ reservedTokens: await fixedPromptTokens(),
556
578
  toldLedger: host.toldLedger?.render(sessionId),
557
579
  mode: 'text',
558
580
  loopHooks,
@@ -564,7 +586,7 @@ export async function* runAgentLoopTextTurnRun(input) {
564
586
  model: llmModel,
565
587
  compactionConfig: host.config.compaction,
566
588
  contextWindow,
567
- reservedTokens: fixedPromptTokens(),
589
+ reservedTokens: await fixedPromptTokens(),
568
590
  toldLedger: host.toldLedger?.render(sessionId),
569
591
  mode: 'text',
570
592
  force: true,
@@ -644,7 +666,9 @@ export async function* runAgentLoopTextTurnRun(input) {
644
666
  const budgetNote = explorationBudget?.record(resolvedName, isProgress(progressKind));
645
667
  const finalText = budgetNote ? `${resultText}\n\n${budgetNote}` : resultText;
646
668
  toolCalls.push({ tool: resolvedName, args: effectiveArgs, result: finalText });
647
- return toolResultToAgentMessage(toolCall, finalText, false);
669
+ const result = toolResultToAgentMessage(toolCall, finalText, false);
670
+ const blocks = deferredController?.modelOutputBlocks?.(resolvedName, effectiveArgs, finalText);
671
+ return blocks ? { ...result, content: blocks } : result;
648
672
  }
649
673
  catch (err) {
650
674
  if (err instanceof TurnJournalCommitError)
@@ -719,8 +743,7 @@ export async function* runAgentLoopTextTurnRun(input) {
719
743
  // Messages of the running segment, for progress summaries before agent_end.
720
744
  let segmentMessages = [];
721
745
  function* drainCompletionUsage() {
722
- for (const usage of completionUsage.drainUsage()) {
723
- const event = turnUsageEvent(usage);
746
+ for (const event of completionUsage.drainEvents()) {
724
747
  emitTurnEvent(event);
725
748
  yield event;
726
749
  }
@@ -773,7 +796,7 @@ export async function* runAgentLoopTextTurnRun(input) {
773
796
  lastAssistantNeedsHostReply = Boolean(assistant.errorMessage)
774
797
  || (!lastAssistantHadToolCalls && !answerText.trim());
775
798
  lastAssistantText = imReply && !lastAssistantNeedsHostReply ? answerText
776
- : resolveAssistantReplyText(imReply ? { ...assistant, content: [] } : assistant);
799
+ : resolveAssistantReplyText(imReply ? { ...assistant, content: [] } : assistant, chatFailure);
777
800
  logAssistantIterationFailure(assistant, modelId, sessionId);
778
801
  const toolNames = assistant.content
779
802
  .filter((b) => b.type === 'toolCall')
@@ -900,10 +923,14 @@ export async function* runAgentLoopTextTurnRun(input) {
900
923
  hostReply = !summary.assistant;
901
924
  }
902
925
  const structuredReply = imReply && !hostReply;
903
- const continuationPrefix = continuations > 0
926
+ // A chat member does not announce how many steps a task took; only non-IM callers get the notice.
927
+ const continuationPrefix = continuations > 0 && input.turnContext.origin.kind !== 'im'
904
928
  ? `(任务步骤较多,已自动续跑 ${continuations} 次)\n\n` : '';
905
929
  const proseReply = structuredReply ? ''
906
- : sanitizeAssistantReply(rawReply, { toolSummary: formatToolCallsForUser(toolCalls) });
930
+ : sanitizeAssistantReply(rawReply, {
931
+ toolSummary: formatToolCallsForUser(toolCalls),
932
+ ...(chatFailure ? { emptyMessage: chatFailureLine('malformed_tool_call', chatFailure) } : {}),
933
+ });
907
934
  const textReply = delegatedOnly ? '' : proseReply.trim() ? continuationPrefix + proseReply : proseReply;
908
935
  let output = delegatedOnly || structuredReply ? [] : parseOutput(textReply);
909
936
  if (!delegatedOnly && structuredReply) {
@@ -911,26 +938,33 @@ export async function* runAgentLoopTextTurnRun(input) {
911
938
  output = decodeStructuredReply(rawReply, input.replySegments);
912
939
  }
913
940
  catch (error) {
914
- const recovery = await recoverStructuredReply({
915
- transport: completionUsage.transport, model: llmModel, raw: rawReply,
916
- error: error instanceof Error ? error : new Error('Invalid structured reply'), signal,
917
- segments: input.replySegments,
918
- });
919
- output = recovery.output;
920
- // History must match what the user saw: the corrected reply, or the failure text. Never the
921
- // correction prompt (it embeds the rejected raw text) and never the rejected raw reply.
922
941
  const messages = conversationBatches.at(-1)?.messages;
923
942
  const rejected = messages?.at(-1);
924
- if (messages && rejected?.role === 'assistant') {
925
- messages[messages.length - 1] = recovery.assistant
926
- ?? { ...rejected, content: [{ type: 'text', text: STRUCTURED_REPLY_FAILURE }] };
943
+ const lenient = decodeLenientStructuredReply(rawReply, input.replySegments);
944
+ if (lenient) {
945
+ output = lenient.output;
946
+ if (messages && rejected?.role === 'assistant') {
947
+ messages[messages.length - 1] = { ...rejected, content: [{ type: 'text', text: lenient.text }] };
948
+ }
949
+ }
950
+ else {
951
+ const recovery = await recoverStructuredReply({
952
+ transport: completionUsage.transport, model: llmModel, raw: rawReply,
953
+ error: error instanceof Error ? error : new Error('Invalid structured reply'), signal,
954
+ segments: input.replySegments,
955
+ failureText: chatFailureLine('format', { ...chatFailure, random: 0 }),
956
+ });
957
+ output = recovery.output;
958
+ // History must match what the user saw: the corrected reply, or the failure text. Never the
959
+ // correction prompt (it embeds the rejected raw text) and never the rejected raw reply.
960
+ if (messages && rejected?.role === 'assistant') {
961
+ messages[messages.length - 1] = recovery.assistant
962
+ ?? { ...rejected, content: [{ type: 'text', text: renderAgentOutput(recovery.output) }] };
963
+ }
927
964
  }
928
965
  }
929
966
  }
930
967
  if (!delegatedOnly) {
931
- if (structuredReply && continuationPrefix && output[0]?.type === 'message') {
932
- output[0].segments.unshift({ type: 'text', data: { text: continuationPrefix } });
933
- }
934
968
  output = mergeToolOutboundElements(output, toolCalls);
935
969
  }
936
970
  const finalReply = imReply ? renderAgentOutput(output) : textReply;