chainlesschain 0.166.26 → 0.166.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-Z8_dnLd8.js → AIOps-TeJoc3XQ.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C4bKCISV.js → ActionButton-SZ9klC71.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-GztAv2jy.js → Analytics-BvxMGhXs.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DEqNcuVx.js → AppLayout-5zUWvYnk.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D-R4Ehap.js → Artifacts-0BfSQj3N.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-mzzdTIlM.js → Audit-cScBdt3p.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DS2QupIC.js → BackgroundAgents-022mS_uG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CQSirC39.js → Backup-CWnqpCpj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bhuc-eOV.js → BaseInput-BCp80_Kc.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-B-RLVGnt.js → Chat-B2rj5NdZ.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CBbMoVuE.js → ChatBubbleRenderer-2Lw_8bkr.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DxwgBryW.js → Checkbox-BTHrb0x7.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Cwrm-7nh.js → Codegen-Y17WGsCF.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DrSCT-nF.js → Col-BBBucqHI.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DY2D9WNs.js → Community-B2qcNGY4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-o-tV0URw.js → Compact-BhDyZd93.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bndh6S8f.js → Compliance-BUEgTVrI.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CSNQODak.js → Cowork-_wey4IKY.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CTgxRHdy.js → Cron-BSO7RSdx.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BJoGQzDI.js → Crosschain-DawYCk-u.js} +1 -1
- package/src/assets/web-panel/assets/{DID-V0Ztm8ne.js → DID-DvtgImFY.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B-38THOj.js → Dashboard-DYAyw4yL.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B3FdOwMO.js → Dropdown-CIaenBpM.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-YQDz4mUF.js → EmailListRenderer-jKv7wz64.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DYE45IF9.js → FamilyGuardDashboard-BxQggJuN.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DjBFjdRZ.js → Federation-CDi_BOHj.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVTXEa0R.js → FormItemContext-C-7XWf-w.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B6Tkke8X.js → GenericCardRenderer-D8PAhWue.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CD0hyAzT.js → Git-yhajzO5I.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CWOQQJr8.js → Governance-DjM6hZK3.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-lHYwUk-C.js → Inference-C-gVn1ci.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Cledxv7t.js → KnowledgeGraph-CyG5TgH-.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D45mt-rM.js → Logs-CnIbZJc3.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BDfOgqr1.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Yy30UZPw.js → Marketplace-BQry6ziq.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cil5l-bh.js → McpTools-7MBu8YsX.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DdJcnyqy.js → Memory-BlaZL_vM.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-IrzFYHEv.js → MobileBridge-iUYdZA22.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D8q64LMd.js → MobileProjects-CUO-OpoO.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CpdTJzW7.js → Mtc-6sD7414H.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-C7Tvw2m6.js → MtcAudit-BW8hjlD8.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-Dmj9F5Kr.js → Multisig-BA7_DWLu.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C9Xchob5.js → NLProgramming-7P7H52-B.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Bzq6Yry5.js → Notes-D2O2aG0R.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DxG9eKb4.js → NotificationSettings-SD4fdxcr.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BS_IYoCe.js → OrderTableRenderer-BbXM4Ueq.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-kazsRvcq.js → Organization-DNabgtKX.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-i4wP-hwn.js → Overflow-B7Bqrhq0.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CfGeU3h-.js → P2P-Dh0Df0zq.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CS-VhVUx.js → PdhVaultBrowser-DHfJarF9.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DnEHXs0I.js → Permissions-Bz3ddnZ9.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DeD82qke.js → PersonalDataHub-Dfe0X6p8.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-CMGGLgBY.js → Pipeline-kCnDqnwc.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CvHoFv32.js → Privacy-CPveUX9G.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DnJapCc1.js → ProjectInit-ddXd7WTL.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CqVdv3-g.js → ProjectSettings-DnXcEjyu.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-B_o8vcs_.js → Projects-O2yaPDuU.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CPKseoDW.js → Providers-hJpcyGxR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-_iBfKbPQ.js → QrScannerModal-CkbMoodn.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CMrlZ69a.js → QuickAsk-CyFailNZ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BwDtSZ0l.js → Recommend-jpIDoqHs.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DomUAtn4.js → RemoteSession-CieiVXM-.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Cuu2GCHr.js → Reputation-DHtgC0AY.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C7GVts31.js → Row-CSUyTUkR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DRKvgEog.js → RssFeed-CkHylSMs.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BGI9wOeA.js → Search-ykyn3OaJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BlLaHy_L.js → Security-DjM95Eyb.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DFqZbjmX.js → Services-Cf4UMvka.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BCGxdooP.js → Skeleton-BnR-ZNiM.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CwFKgcmm.js → Skills-oC5jYJgK.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CiXXh9eS.js → Sla-CTc_YFxW.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-WSx2CLqO.js → SpeechSettings-31rddoaP.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BBIqQOtW.js → SyncSettings-C-h00U59.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D1BGLjO1.js → Tasks-CGn_IiAO.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CUIl9axA.js → Templates-B9Hz_RuF.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFZJ0hNz.js → Tenant-CJnIeKrI.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Cw-3eYVn.js → Terminal-DfGtwDMA.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-bOVPB56L.js → TimelineRenderer-CISmCIFj.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Czfyq2VY.js → Tokens-DNr-VU6W.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-C-o4uJY0.js → Trigger-CCYolBXm.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BKq_S6iF.js → Trust-DD75_Hjo.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-fKlWUvMx.js → UkeySign-BXulgGCu.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DB5MLuS7.js → VideoEditing-B0DgHQiZ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-WGXBn7IS.js → Wallet-BV3CEPVS.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B8TC4EMF.js → WebAuthn-CI9bXloP.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DLY--KmM.js → WorkflowEditor-B4z18zZp.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Crw3XQaA.js → chat-BsQ4S4Nh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CxhWauPV.js → colors-C0Jsl3u8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cp3ojtk5.js → compact-item-CMZ_5Gdj.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CRuRXjSo.js → createContext-VeTZ9pjJ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ChXQhXs6.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-XGW474dj.js → hasIn-R2ip_4t-.js} +1 -1
- package/src/assets/web-panel/assets/{index-38TRdZb6.js → index-02inSVeg.js} +1 -1
- package/src/assets/web-panel/assets/{index-C73pqyDb.js → index-0LEUdXks.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5fJS-kn.js → index-4i3UILe7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoUC5_ry.js → index-6vvyh4N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHNixOg_.js → index-B53MC1iz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDIpH_8Q.js → index-BDl3jun-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfPLIA-f.js → index-BGFKUUsv.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxOtBFVy.js → index-BGKhfuHU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXnNbbwa.js → index-BJvl_XUa.js} +1 -1
- package/src/assets/web-panel/assets/{index-qzmihx1d.js → index-BPTTGRU9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BetOkRLs.js → index-BX_8_UFn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTE32qol.js → index-BdwYZI-H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ctu98x57.js → index-Bhz0a_oU.js} +1 -1
- package/src/assets/web-panel/assets/{index-jrIcbPUa.js → index-BjW7Qlh-.js} +1 -1
- package/src/assets/web-panel/assets/index-BpqhSIKB.js +1 -0
- package/src/assets/web-panel/assets/{index-BUWZ57T4.js → index-BtV-unhh.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8UOVaUH.js → index-ByPe74vs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BElgeKJ0.js → index-Bz4O8exa.js} +3 -3
- package/src/assets/web-panel/assets/{index-DUSVsOGS.js → index-C3oYV_lA.js} +1 -1
- package/src/assets/web-panel/assets/{index-De2FN0xN.js → index-CIanQUmB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdlfrcC-.js → index-CNMfIBsh.js} +1 -1
- package/src/assets/web-panel/assets/{index-QqAuH7-S.js → index-CsoYgCfM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPFam-me.js → index-CuIo38St.js} +1 -1
- package/src/assets/web-panel/assets/index-Cv9p7aNY.js +1 -0
- package/src/assets/web-panel/assets/{index-DhomnuaB.js → index-D4DUCUMd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYkjs7eP.js → index-DF8AVnyq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBcBsJYg.js → index-DUfRaQ2R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW028Pwn.js → index-DdXJoar7.js} +1 -1
- package/src/assets/web-panel/assets/{index-Yq3bFbYX.js → index-DjkLws9g.js} +1 -1
- package/src/assets/web-panel/assets/{index-J_08R33H.js → index-Dqf95oif.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVmSfIkJ.js → index-Du20UePt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuVA26SD.js → index-DvYF07ij.js} +1 -1
- package/src/assets/web-panel/assets/{index-rsACoVT9.js → index-DwfM7rYW.js} +1 -1
- package/src/assets/web-panel/assets/{index-WvNZXbIO.js → index-WJ_YxlwK.js} +1 -1
- package/src/assets/web-panel/assets/{index-E-RSfImO.js → index-Zlt4hVX9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dp2nC5On.js → index-iHgBGK8l.js} +1 -1
- package/src/assets/web-panel/assets/{index-nJR64pA_.js → index-nM2lMEZ4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B45Vkh3T.js → index-q_5BV7Pj.js} +1 -1
- package/src/assets/web-panel/assets/{index-W8_0yAi_.js → index-yIN1stc7.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DAu-hgme.js → initDefaultProps-D1uCn3z4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Db8yk0Hu.js → motion-5TP3EY0Q.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cii2jOin.js → move-C_bi-KUq.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CnZBDkyZ.js → mtc-parser-CmdzT6ro.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Buzt1FTL.js → omit-Ce0_wQke.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CpZ_NmE6.js → pickAttrs-DG8auaWM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-mBrx3dYm.js → placementArrow-CaWVLdLv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DC_QzljB.js → responsiveObserve-Bot4LFBx.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DT_UVED-.js → slide-HCFYDpKc.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-78t6eNLV.js → statusUtils-CWNqwWxQ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CV_T1ULL.js → styleChecker-BUyR4PCy.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-s8edGuXD.js → useFlexGapSupport-C4XRVB5s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Pk9dH2HC.js → useFs-D2VOJbk-.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-B_GqAm8c.js → usePersonalDataHub-dN8G1tq6.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Cxyss24q.js → vnode-DSQKKZnw.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-0UY3cOuc.js → zoom-Dl0bVMqQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/ask.js +84 -5
- package/src/commands/chat.js +67 -1
- package/src/commands/compact.js +81 -2
- package/src/commands/complete.js +64 -5
- package/src/commands/cowork.js +133 -0
- package/src/commands/orchestrate.js +18 -7
- package/src/data/changelog.json +13 -0
- package/src/gateways/ws/action-protocol.js +2 -0
- package/src/lib/agent-router.js +116 -36
- package/src/lib/cowork-task-runner.js +107 -4
- package/src/lib/evolution/agent-evolution-ingress.js +231 -40
- package/src/lib/evolution/agent-evolution-runtime-composition.js +85 -5
- package/src/lib/evolution/evolution-deployment-loader.js +17 -1
- package/src/lib/evolution/evolution-evidence-projector.js +25 -6
- package/src/lib/model-failure-policy.js +10 -0
- package/src/lib/orchestrator.js +97 -27
- package/src/lib/read-file-loop-guard.js +21 -1
- package/src/lib/sub-agent-context.js +34 -2
- package/src/repl/chat-repl.js +60 -10
- package/src/runtime/agent-core.js +96 -5
- package/src/runtime/agent-runtime.js +20 -1
- package/src/runtime/runtime-factory.js +1 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DJjPaOnM.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CKAQY2zn.js +0 -1
- package/src/assets/web-panel/assets/index-Cetfwq8j.js +0 -1
- package/src/assets/web-panel/assets/index-DOpcRO2-.js +0 -1
package/src/lib/orchestrator.js
CHANGED
|
@@ -26,7 +26,9 @@ import crypto from "crypto";
|
|
|
26
26
|
import { AgentRouter } from "./agent-router.js";
|
|
27
27
|
import { NotificationManager } from "./notifiers/index.js";
|
|
28
28
|
import { createChatFn } from "./cowork-adapter.js";
|
|
29
|
+
import { captureAgentEvolutionRuntimeComposition } from "./evolution/agent-evolution-runtime-composition-brand.js";
|
|
29
30
|
import { firstBalancedJson } from "./json-schema-output.js";
|
|
31
|
+
import { isTerminalModelFailure } from "./model-failure-policy.js";
|
|
30
32
|
import {
|
|
31
33
|
assertCLILegacyMutationAllowed,
|
|
32
34
|
cliLegacyRuntimeReadOnly,
|
|
@@ -58,6 +60,7 @@ export const TASK_STATUS = {
|
|
|
58
60
|
CI_PASSED: "ci-passed",
|
|
59
61
|
CI_FAILED: "ci-failed",
|
|
60
62
|
RETRYING: "retrying",
|
|
63
|
+
FINALIZING: "finalizing",
|
|
61
64
|
COMPLETED: "completed",
|
|
62
65
|
FAILED: "failed",
|
|
63
66
|
};
|
|
@@ -98,6 +101,15 @@ export class Orchestrator extends EventEmitter {
|
|
|
98
101
|
this.maxRetries = options.maxRetries || 3;
|
|
99
102
|
this.ciCommand = options.ciCommand || "npm test";
|
|
100
103
|
this.verbose = options.verbose || false;
|
|
104
|
+
this._evolutionCompositionFactory =
|
|
105
|
+
options.evolutionCompositionFactory ?? null;
|
|
106
|
+
if (
|
|
107
|
+
this._evolutionCompositionFactory !== null &&
|
|
108
|
+
typeof this._evolutionCompositionFactory !== "function"
|
|
109
|
+
) {
|
|
110
|
+
throw new TypeError("evolutionCompositionFactory must be a function");
|
|
111
|
+
}
|
|
112
|
+
this._llmOptions = options.llm || {};
|
|
101
113
|
|
|
102
114
|
// Cap retained task history. Every addTask stores a record in _tasks and
|
|
103
115
|
// nothing ever removes it, so a long-running orchestrator (cron-watch /
|
|
@@ -141,7 +153,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
141
153
|
this.notifier =
|
|
142
154
|
options.notifier || NotificationManager.fromEnv(options.notify || {});
|
|
143
155
|
|
|
144
|
-
this._chat = createChatFn(
|
|
156
|
+
this._chat = createChatFn(this._llmOptions);
|
|
145
157
|
this._cronTimer = null;
|
|
146
158
|
|
|
147
159
|
// Forward router events
|
|
@@ -210,7 +222,51 @@ export class Orchestrator extends EventEmitter {
|
|
|
210
222
|
.catch(() => {});
|
|
211
223
|
}
|
|
212
224
|
|
|
213
|
-
|
|
225
|
+
let evolutionComposition = null;
|
|
226
|
+
try {
|
|
227
|
+
if (this._evolutionCompositionFactory !== null) {
|
|
228
|
+
evolutionComposition = captureAgentEvolutionRuntimeComposition(
|
|
229
|
+
await this._evolutionCompositionFactory(
|
|
230
|
+
Object.freeze({
|
|
231
|
+
mode: "orchestrate",
|
|
232
|
+
runId: task.id,
|
|
233
|
+
taskId: task.id,
|
|
234
|
+
source: task.source,
|
|
235
|
+
cwd: task.cwd,
|
|
236
|
+
}),
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
if (
|
|
240
|
+
evolutionComposition.runId !== task.id ||
|
|
241
|
+
evolutionComposition.evolutionIngress.runId !== task.id ||
|
|
242
|
+
evolutionComposition.evolutionIngress.tenantId !==
|
|
243
|
+
evolutionComposition.tenantId
|
|
244
|
+
) {
|
|
245
|
+
const error = new Error(
|
|
246
|
+
"Orchestrator evolution composition is not bound to the requested Run",
|
|
247
|
+
);
|
|
248
|
+
error.code = "CC_AGENT_EVOLUTION_INGRESS_FAILED";
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
await evolutionComposition.evolutionIngress.start();
|
|
252
|
+
await evolutionComposition.evolutionIngress.ingestUserPrompt({
|
|
253
|
+
content: task.description,
|
|
254
|
+
context: task.context,
|
|
255
|
+
taskId: task.id,
|
|
256
|
+
source: `orchestrate:${task.source}`,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
task.status = TASK_STATUS.FAILED;
|
|
261
|
+
task.error = error.message;
|
|
262
|
+
this.emit("task:failed", { task, error });
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await this._orchestrate(
|
|
267
|
+
task,
|
|
268
|
+
evolutionComposition?.evolutionIngress ?? null,
|
|
269
|
+
);
|
|
214
270
|
return task;
|
|
215
271
|
}
|
|
216
272
|
|
|
@@ -257,24 +313,22 @@ export class Orchestrator extends EventEmitter {
|
|
|
257
313
|
|
|
258
314
|
// ─── Orchestration pipeline ──────────────────────────────────────
|
|
259
315
|
|
|
260
|
-
async _orchestrate(task) {
|
|
316
|
+
async _orchestrate(task, evolutionIngress = null) {
|
|
261
317
|
assertCLILegacyMutationAllowed("Orchestrator._orchestrate");
|
|
262
318
|
try {
|
|
263
319
|
// Step 1: Decompose
|
|
264
|
-
task.subtasks = await this._decompose(task);
|
|
320
|
+
task.subtasks = await this._decompose(task, evolutionIngress);
|
|
265
321
|
task.status = TASK_STATUS.DISPATCHED;
|
|
266
322
|
|
|
267
323
|
// Step 2: Dispatch to Claude Code agents
|
|
268
|
-
task.agentResults = await this._dispatch(task);
|
|
324
|
+
task.agentResults = await this._dispatch(task, evolutionIngress);
|
|
269
325
|
this._assertSuccessfulAgentResults(task);
|
|
270
326
|
|
|
271
327
|
// Step 3: CI/CD check
|
|
272
328
|
if (task.runCI) {
|
|
273
|
-
await this._ciLoop(task);
|
|
329
|
+
await this._ciLoop(task, evolutionIngress);
|
|
274
330
|
} else {
|
|
275
|
-
task
|
|
276
|
-
task.completedAt = new Date().toISOString();
|
|
277
|
-
this.emit("task:complete", task);
|
|
331
|
+
await this._completeTask(task, evolutionIngress);
|
|
278
332
|
}
|
|
279
333
|
} catch (err) {
|
|
280
334
|
task.status = TASK_STATUS.FAILED;
|
|
@@ -284,8 +338,26 @@ export class Orchestrator extends EventEmitter {
|
|
|
284
338
|
}
|
|
285
339
|
}
|
|
286
340
|
|
|
341
|
+
async _completeTask(task, evolutionIngress, ciResult = null) {
|
|
342
|
+
assertCLILegacyMutationAllowed("Orchestrator._completeTask");
|
|
343
|
+
task.status = TASK_STATUS.FINALIZING;
|
|
344
|
+
if (evolutionIngress !== null) await evolutionIngress.complete();
|
|
345
|
+
if (ciResult !== null && this.notifier.isConfigured && task.notify) {
|
|
346
|
+
await this.notifier.notifySuccess({
|
|
347
|
+
taskId: task.id,
|
|
348
|
+
description: task.description.slice(0, 100),
|
|
349
|
+
agentCount: task.subtasks.length,
|
|
350
|
+
duration: Date.now() - new Date(task.createdAt).getTime(),
|
|
351
|
+
filesChanged: ciResult.filesChanged || [],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
task.completedAt = new Date().toISOString();
|
|
355
|
+
task.status = TASK_STATUS.COMPLETED;
|
|
356
|
+
this.emit("task:complete", task);
|
|
357
|
+
}
|
|
358
|
+
|
|
287
359
|
/** LLM-driven task decomposition into coding subtasks. */
|
|
288
|
-
async _decompose(task) {
|
|
360
|
+
async _decompose(task, evolutionIngress = null) {
|
|
289
361
|
assertCLILegacyMutationAllowed("Orchestrator._decompose");
|
|
290
362
|
task.status = TASK_STATUS.DECOMPOSING;
|
|
291
363
|
this.emit("task:decomposing", task);
|
|
@@ -302,7 +374,14 @@ export class Orchestrator extends EventEmitter {
|
|
|
302
374
|
(task.context ? `\nAdditional context:\n${task.context}` : "");
|
|
303
375
|
|
|
304
376
|
try {
|
|
305
|
-
const
|
|
377
|
+
const chat =
|
|
378
|
+
evolutionIngress === null
|
|
379
|
+
? this._chat
|
|
380
|
+
: createChatFn({
|
|
381
|
+
...this._llmOptions,
|
|
382
|
+
evolutionIngress,
|
|
383
|
+
});
|
|
384
|
+
const raw = await chat(
|
|
306
385
|
[
|
|
307
386
|
{ role: "system", content: systemPrompt },
|
|
308
387
|
{ role: "user", content: userMsg },
|
|
@@ -317,7 +396,8 @@ export class Orchestrator extends EventEmitter {
|
|
|
317
396
|
this.emit("task:decomposed", { task, subtasks });
|
|
318
397
|
this._log(`Decomposed into ${subtasks.length} subtask(s)`);
|
|
319
398
|
return subtasks;
|
|
320
|
-
} catch {
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (isTerminalModelFailure(error)) throw error;
|
|
321
401
|
// Fallback: treat whole task as single subtask
|
|
322
402
|
const fallback = [
|
|
323
403
|
{ id: "sub-1", description: task.description, context: task.context },
|
|
@@ -328,7 +408,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
328
408
|
}
|
|
329
409
|
|
|
330
410
|
/** Dispatch subtasks to the Claude Code pool. */
|
|
331
|
-
async _dispatch(task) {
|
|
411
|
+
async _dispatch(task, evolutionIngress = null) {
|
|
332
412
|
assertCLILegacyMutationAllowed("Orchestrator._dispatch");
|
|
333
413
|
this.emit("agents:dispatched", { task, count: task.subtasks.length });
|
|
334
414
|
this._log(
|
|
@@ -337,13 +417,14 @@ export class Orchestrator extends EventEmitter {
|
|
|
337
417
|
|
|
338
418
|
const results = await this._router.dispatch(task.subtasks, {
|
|
339
419
|
cwd: task.cwd,
|
|
420
|
+
...(evolutionIngress === null ? {} : { evolutionIngress }),
|
|
340
421
|
});
|
|
341
422
|
this.emit("agents:complete", { task, results });
|
|
342
423
|
return results;
|
|
343
424
|
}
|
|
344
425
|
|
|
345
426
|
/** Run CI command and retry loop. */
|
|
346
|
-
async _ciLoop(task) {
|
|
427
|
+
async _ciLoop(task, evolutionIngress = null) {
|
|
347
428
|
assertCLILegacyMutationAllowed("Orchestrator._ciLoop");
|
|
348
429
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
349
430
|
task.status = TASK_STATUS.CI_CHECKING;
|
|
@@ -356,22 +437,10 @@ export class Orchestrator extends EventEmitter {
|
|
|
356
437
|
|
|
357
438
|
if (ciResult.pass) {
|
|
358
439
|
task.status = TASK_STATUS.CI_PASSED;
|
|
359
|
-
task.completedAt = new Date().toISOString();
|
|
360
440
|
this.emit("ci:pass", { task });
|
|
361
441
|
this._log(`CI passed for task ${task.id}`);
|
|
362
442
|
|
|
363
|
-
|
|
364
|
-
await this.notifier.notifySuccess({
|
|
365
|
-
taskId: task.id,
|
|
366
|
-
description: task.description.slice(0, 100),
|
|
367
|
-
agentCount: task.subtasks.length,
|
|
368
|
-
duration: Date.now() - new Date(task.createdAt).getTime(),
|
|
369
|
-
filesChanged: ciResult.filesChanged || [],
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
task.status = TASK_STATUS.COMPLETED;
|
|
374
|
-
this.emit("task:complete", task);
|
|
443
|
+
await this._completeTask(task, evolutionIngress, ciResult);
|
|
375
444
|
return;
|
|
376
445
|
}
|
|
377
446
|
|
|
@@ -409,6 +478,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
409
478
|
|
|
410
479
|
task.agentResults = await this._router.dispatch(fixSubtasks, {
|
|
411
480
|
cwd: task.cwd,
|
|
481
|
+
...(evolutionIngress === null ? {} : { evolutionIngress }),
|
|
412
482
|
});
|
|
413
483
|
this._assertSuccessfulAgentResults(task);
|
|
414
484
|
this.emit("agents:complete", { task, results: task.agentResults });
|
|
@@ -34,6 +34,7 @@ export class ReadFileLoopGuard {
|
|
|
34
34
|
this.repeatedBatches = 0;
|
|
35
35
|
this.recoveryOfferedAt = 0;
|
|
36
36
|
this.batch = null;
|
|
37
|
+
this.largeOutputs = new Set();
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
startBatch() {
|
|
@@ -152,6 +153,24 @@ export class ReadFileLoopGuard {
|
|
|
152
153
|
if (result?.readRecovery?.action === "targeted-review")
|
|
153
154
|
this.batch.advanced = true;
|
|
154
155
|
if (result?.readProgress?.newContent === false) this.batch.duplicates++;
|
|
156
|
+
} else if (
|
|
157
|
+
(tool === "run_code" || tool === "run_shell") &&
|
|
158
|
+
typeof result?.output === "string" &&
|
|
159
|
+
result.output.length >= 8000
|
|
160
|
+
) {
|
|
161
|
+
// Arbitrary code may just dump the same file again. Compare returned
|
|
162
|
+
// evidence, not code spelling or timings. Never cache/skip execution:
|
|
163
|
+
// identical stdout does not imply identical filesystem side effects.
|
|
164
|
+
const fingerprint = createHash("sha256")
|
|
165
|
+
.update(result.output)
|
|
166
|
+
.digest("hex");
|
|
167
|
+
if (this.largeOutputs.has(fingerprint)) this.batch.duplicates++;
|
|
168
|
+
else {
|
|
169
|
+
this.largeOutputs.add(fingerprint);
|
|
170
|
+
this.batch.advanced = true;
|
|
171
|
+
while (this.largeOutputs.size > 64)
|
|
172
|
+
this.largeOutputs.delete(this.largeOutputs.values().next().value);
|
|
173
|
+
}
|
|
155
174
|
} else if (!DISCOVERY_TOOLS.has(tool)) {
|
|
156
175
|
this.batch.substantive = true;
|
|
157
176
|
}
|
|
@@ -163,6 +182,7 @@ export class ReadFileLoopGuard {
|
|
|
163
182
|
this.repeatedBatches = 0;
|
|
164
183
|
this.recoveryOfferedAt = 0;
|
|
165
184
|
if (this.batch.substantive) {
|
|
185
|
+
this.largeOutputs.clear();
|
|
166
186
|
for (const entry of this.progress.values()) entry.rereads.clear();
|
|
167
187
|
}
|
|
168
188
|
} else if (this.batch.duplicates) this.repeatedBatches++;
|
|
@@ -172,7 +192,7 @@ export class ReadFileLoopGuard {
|
|
|
172
192
|
|
|
173
193
|
get recoveryHint() {
|
|
174
194
|
return this.repeatedBatches >= 1
|
|
175
|
-
? "Repeated
|
|
195
|
+
? "Repeated file reads or identical large command outputs detected. Do not switch to run_code/run_shell to dump the same file again. Filter/count locally and print only relevant rows, totals and source line numbers, or use a focused search. The runtime continues unfinished read_file scans automatically and returns use-findings after EOF. Use the retained outline to perform the user's task. Do not restart a whole-file scan after context compaction; answer once the requested evidence is sufficient."
|
|
176
196
|
: null;
|
|
177
197
|
}
|
|
178
198
|
|
|
@@ -12,6 +12,8 @@ import crypto from "crypto";
|
|
|
12
12
|
import { CLIContextEngineering } from "./cli-context-engineering.js";
|
|
13
13
|
import { agentLoop, buildSystemPrompt, AGENT_TOOLS } from "./agent-core.js";
|
|
14
14
|
import { feature } from "./feature-flags.js";
|
|
15
|
+
import { isEvolutionIngressFailure } from "./model-failure-policy.js";
|
|
16
|
+
import { captureAgentEvolutionIngress } from "./evolution/agent-evolution-ingress.js";
|
|
15
17
|
import {
|
|
16
18
|
createWorktree,
|
|
17
19
|
removeWorktree,
|
|
@@ -589,7 +591,13 @@ export class SubAgentContext {
|
|
|
589
591
|
}
|
|
590
592
|
return result;
|
|
591
593
|
} catch (err) {
|
|
592
|
-
if (
|
|
594
|
+
if (
|
|
595
|
+
isEvolutionIngressFailure(err) ||
|
|
596
|
+
err?.runtimeLedgerPersistence === true ||
|
|
597
|
+
this._workflowEffectId
|
|
598
|
+
) {
|
|
599
|
+
this.status = "failed";
|
|
600
|
+
this.completedAt = new Date().toISOString();
|
|
593
601
|
throw err;
|
|
594
602
|
}
|
|
595
603
|
// If worktree creation fails (e.g. not a git repo), fall back to direct
|
|
@@ -875,12 +883,21 @@ export class SubAgentContext {
|
|
|
875
883
|
options.approvalGate = this._approvalGate;
|
|
876
884
|
}
|
|
877
885
|
|
|
886
|
+
const evolutionIngress =
|
|
887
|
+
options.evolutionIngress == null
|
|
888
|
+
? null
|
|
889
|
+
: captureAgentEvolutionIngress(options.evolutionIngress);
|
|
890
|
+
if (evolutionIngress !== null) options.evolutionIngress = evolutionIngress;
|
|
891
|
+
|
|
878
892
|
try {
|
|
879
893
|
// Use a separate messages array for the agent loop
|
|
880
894
|
// The agentLoop will append to this.messages directly
|
|
881
895
|
const gen = agentLoop(this.messages, options);
|
|
882
896
|
|
|
883
897
|
for await (const event of gen) {
|
|
898
|
+
if (evolutionIngress !== null) {
|
|
899
|
+
await evolutionIngress.ingestAgentEvent(event);
|
|
900
|
+
}
|
|
884
901
|
this._iterationCount++;
|
|
885
902
|
|
|
886
903
|
if (event.type === "run-started" && event.runId) {
|
|
@@ -1099,6 +1116,13 @@ export class SubAgentContext {
|
|
|
1099
1116
|
this._tokenCount += Math.ceil((lastContent.length || 0) / 4);
|
|
1100
1117
|
}
|
|
1101
1118
|
|
|
1119
|
+
if (event.type === "run-ended" && event.reason !== "complete") {
|
|
1120
|
+
this.forceComplete(event.reason || "unknown-terminal-reason", {
|
|
1121
|
+
partialContent: lastContent,
|
|
1122
|
+
artifacts,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1102
1126
|
// Emit progress to consumer if callback provided
|
|
1103
1127
|
if (this._onProgress) {
|
|
1104
1128
|
try {
|
|
@@ -1161,7 +1185,13 @@ export class SubAgentContext {
|
|
|
1161
1185
|
throw error;
|
|
1162
1186
|
}
|
|
1163
1187
|
} catch (err) {
|
|
1164
|
-
if (
|
|
1188
|
+
if (
|
|
1189
|
+
isEvolutionIngressFailure(err) ||
|
|
1190
|
+
err?.runtimeLedgerPersistence === true ||
|
|
1191
|
+
this._workflowEffectId
|
|
1192
|
+
) {
|
|
1193
|
+
this.status = "failed";
|
|
1194
|
+
this.completedAt = new Date().toISOString();
|
|
1165
1195
|
throw err;
|
|
1166
1196
|
}
|
|
1167
1197
|
if (this.isAborted()) {
|
|
@@ -1295,6 +1325,8 @@ export class SubAgentContext {
|
|
|
1295
1325
|
: "";
|
|
1296
1326
|
this.result = {
|
|
1297
1327
|
summary: `(Sub-agent force-completed: ${reason})${partialText}`,
|
|
1328
|
+
incomplete: true,
|
|
1329
|
+
completionReason: reason,
|
|
1298
1330
|
artifacts: Array.isArray(partial.artifacts) ? partial.artifacts : [],
|
|
1299
1331
|
tokenCount: this._tokenCount,
|
|
1300
1332
|
toolsUsed: [...new Set(this._toolsUsed)],
|
package/src/repl/chat-repl.js
CHANGED
|
@@ -56,6 +56,8 @@ import { installPipeSafety } from "../runtime/pipe-safety.js";
|
|
|
56
56
|
import { installOutputBackpressure } from "../runtime/output-backpressure.js";
|
|
57
57
|
import { assertChatSessionUsageAdmission } from "../lib/chat-session-admission.js";
|
|
58
58
|
import { captureAmbientExecutionLocation } from "../lib/execution-location-runtime.js";
|
|
59
|
+
import { captureAgentEvolutionIngress } from "../lib/evolution/agent-evolution-ingress.js";
|
|
60
|
+
import { createAgentEvolutionSessionLifecycle } from "../lib/evolution/agent-evolution-session-lifecycle.js";
|
|
59
61
|
|
|
60
62
|
const SLASH_COMMANDS = {
|
|
61
63
|
"/exit": "Exit the chat",
|
|
@@ -94,9 +96,17 @@ const SLASH_COMMANDS = {
|
|
|
94
96
|
* @param {object} options
|
|
95
97
|
*/
|
|
96
98
|
export async function startChatRepl(options = {}) {
|
|
99
|
+
const evolutionIngress =
|
|
100
|
+
options.evolutionIngress == null
|
|
101
|
+
? null
|
|
102
|
+
: captureAgentEvolutionIngress(options.evolutionIngress);
|
|
97
103
|
const admittedSessionId = options.sessionId
|
|
98
104
|
? assertChatSessionUsageAdmission(options.sessionId)
|
|
99
105
|
: null;
|
|
106
|
+
const evolutionSession =
|
|
107
|
+
evolutionIngress === null
|
|
108
|
+
? null
|
|
109
|
+
: createAgentEvolutionSessionLifecycle(evolutionIngress);
|
|
100
110
|
let model = options.model || "qwen2:7b";
|
|
101
111
|
let provider = options.provider || "ollama";
|
|
102
112
|
const baseUrl = options.baseUrl || "http://localhost:11434";
|
|
@@ -147,7 +157,9 @@ export async function startChatRepl(options = {}) {
|
|
|
147
157
|
),
|
|
148
158
|
);
|
|
149
159
|
try {
|
|
150
|
-
|
|
160
|
+
const createInterface =
|
|
161
|
+
options.createInterface || readline.createInterface.bind(readline);
|
|
162
|
+
rl = createInterface({
|
|
151
163
|
input: options.input || process.stdin,
|
|
152
164
|
output: stdout,
|
|
153
165
|
prompt: chalk.green("you> "),
|
|
@@ -266,13 +278,20 @@ export async function startChatRepl(options = {}) {
|
|
|
266
278
|
logger.verbose?.(`[@ref] expansion skipped: ${err.message}`);
|
|
267
279
|
}
|
|
268
280
|
|
|
269
|
-
|
|
270
|
-
|
|
281
|
+
try {
|
|
282
|
+
if (evolutionIngress !== null) {
|
|
283
|
+
await evolutionIngress.ingestUserPrompt({
|
|
284
|
+
content: userContent,
|
|
285
|
+
sessionId,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
271
288
|
|
|
272
|
-
|
|
273
|
-
|
|
289
|
+
// Add user message
|
|
290
|
+
messages.push({ role: "user", content: userContent });
|
|
291
|
+
|
|
292
|
+
// Stream the response
|
|
293
|
+
stdout.write(chalk.blue("ai> "));
|
|
274
294
|
|
|
275
|
-
try {
|
|
276
295
|
let response;
|
|
277
296
|
const onToken = (token) => {
|
|
278
297
|
const accepted = stdout.write(token);
|
|
@@ -302,9 +321,19 @@ export async function startChatRepl(options = {}) {
|
|
|
302
321
|
if (sessionId)
|
|
303
322
|
appendEvent(sessionId, "user_message", { content: trimmed });
|
|
304
323
|
|
|
324
|
+
const providerMessages =
|
|
325
|
+
evolutionIngress === null
|
|
326
|
+
? messages
|
|
327
|
+
: (
|
|
328
|
+
await evolutionIngress.prepareModelRequest({
|
|
329
|
+
messages,
|
|
330
|
+
tools: [],
|
|
331
|
+
})
|
|
332
|
+
).messages;
|
|
333
|
+
|
|
305
334
|
if (provider === "ollama") {
|
|
306
335
|
response = await streamOllama(
|
|
307
|
-
|
|
336
|
+
providerMessages,
|
|
308
337
|
model,
|
|
309
338
|
baseUrl,
|
|
310
339
|
onToken,
|
|
@@ -325,7 +354,7 @@ export async function startChatRepl(options = {}) {
|
|
|
325
354
|
`API key required for anthropic (set ${providerDef?.apiKeyEnv || "ANTHROPIC_API_KEY"})`,
|
|
326
355
|
);
|
|
327
356
|
response = await streamAnthropic(
|
|
328
|
-
|
|
357
|
+
providerMessages,
|
|
329
358
|
model,
|
|
330
359
|
url,
|
|
331
360
|
key,
|
|
@@ -348,7 +377,7 @@ export async function startChatRepl(options = {}) {
|
|
|
348
377
|
`API key required for ${provider} (set ${providerDef?.apiKeyEnv || "API key"})`,
|
|
349
378
|
);
|
|
350
379
|
response = await streamOpenAI(
|
|
351
|
-
|
|
380
|
+
providerMessages,
|
|
352
381
|
model,
|
|
353
382
|
url,
|
|
354
383
|
key,
|
|
@@ -358,6 +387,14 @@ export async function startChatRepl(options = {}) {
|
|
|
358
387
|
);
|
|
359
388
|
}
|
|
360
389
|
|
|
390
|
+
if (evolutionIngress !== null) {
|
|
391
|
+
await evolutionIngress.ingestAgentEvent({
|
|
392
|
+
type: "response-complete",
|
|
393
|
+
content: response,
|
|
394
|
+
sessionId,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
361
398
|
stdout.write("\n\n");
|
|
362
399
|
messages.push({ role: "assistant", content: response });
|
|
363
400
|
|
|
@@ -410,6 +447,19 @@ export async function startChatRepl(options = {}) {
|
|
|
410
447
|
: 0;
|
|
411
448
|
disposePipeSafety();
|
|
412
449
|
outputFlow.restore();
|
|
413
|
-
|
|
450
|
+
if (evolutionSession !== null) {
|
|
451
|
+
try {
|
|
452
|
+
await evolutionSession.close(outputFailure);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
outputFailure ||= error;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const finalExitCode = outputFailure
|
|
458
|
+
? outputFailure.code === "EPIPE"
|
|
459
|
+
? 0
|
|
460
|
+
: 1
|
|
461
|
+
: exitCode;
|
|
462
|
+
process.exit(finalExitCode);
|
|
414
463
|
});
|
|
464
|
+
return evolutionSession?.handle;
|
|
415
465
|
}
|