@tea-agent/loop-agent 0.42.0-next.2 → 0.42.0-next.3
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/CHANGELOG.md +242 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +942 -187
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +34 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/pi-runtime.js +173 -7
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +30 -19
- package/dist/worker/console/chat/session-store.js +117 -70
- package/dist/worker/console/chat/shortcuts.js +1 -1
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CeznJ4Iw.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-B4u9u7SX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-oghpT81p.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-BPJ5Kvcj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-CoIu69pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-DmIXqJ6B.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-DtgiAsSc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-YSu_EVSD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-DaRO-NB6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-Dh0ZKiGA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DOrvbR-H.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-C3b22LN2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-CXlqO8l6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-BblTBrDu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-DswqPALe.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-CfTwVOlc.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-bLD8jVFo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-BvoG1fh5.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-DEPAdJu3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-DmrNvBLy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-CdwzIwvs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-CJ3hj-md.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-C6YJT2a4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-C_LPfjhX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-xvsSb5Ie.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-Rxp2tKcD.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-DRyLYmZh.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-TLSU_D2B.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DGqeTvHC.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-BZxyH2wh.js} +1 -1
- package/dist/worker/console/static/assets/index-C8V8K9b4.js +451 -0
- package/dist/worker/console/static/assets/index-Cnf_KxTQ.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-BsYtzx01.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-Qr-ezGv-.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-DYbB3ibo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-BHJHewZO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-CmPIPmCG.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-F--C1uV0.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-YTXfL1st.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-DdDMYXP9.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-C3nH4KVd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-kXyxjG7Y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-KNzbl-7w.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-CSwI6dG0.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-Bl1AyuXy.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-2y4EmXQa.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-E8v8YrMg.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-eGGlR856.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8e4J3at.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-B7g7xc07.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Do88I1Nb.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-DR3VCHBG.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-LUvapZIy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-C7GDcaXq.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CbNAqoGK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +907 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +677 -492
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +433 -126
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `createDefaultPiSdkBindings()`.
|
|
27
27
|
*/
|
|
28
28
|
import { createHash, randomBytes } from "node:crypto";
|
|
29
|
-
import { access } from "node:fs/promises";
|
|
29
|
+
import { access, stat } from "node:fs/promises";
|
|
30
30
|
import path from "node:path";
|
|
31
31
|
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
32
|
import { parseProviderError, } from "./provider-error.js";
|
|
@@ -931,6 +931,44 @@ export class PiSessionInitFailedError extends Error {
|
|
|
931
931
|
this.name = "PiSessionInitFailedError";
|
|
932
932
|
}
|
|
933
933
|
}
|
|
934
|
+
async function readModelConfigFileVersion(filePath) {
|
|
935
|
+
try {
|
|
936
|
+
const value = await stat(filePath, { bigint: true });
|
|
937
|
+
return {
|
|
938
|
+
exists: true,
|
|
939
|
+
dev: value.dev,
|
|
940
|
+
ino: value.ino,
|
|
941
|
+
size: value.size,
|
|
942
|
+
mtimeNs: value.mtimeNs,
|
|
943
|
+
ctimeNs: value.ctimeNs,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
catch (error) {
|
|
947
|
+
if (error !== null &&
|
|
948
|
+
typeof error === "object" &&
|
|
949
|
+
"code" in error &&
|
|
950
|
+
error.code === "ENOENT") {
|
|
951
|
+
return { exists: false };
|
|
952
|
+
}
|
|
953
|
+
throw new Error("MODEL_CONFIG_CHECK_FAILED");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
function sameModelConfigFileVersion(left, right) {
|
|
957
|
+
if (!left.exists || !right.exists)
|
|
958
|
+
return left.exists === right.exists;
|
|
959
|
+
return (left.dev === right.dev &&
|
|
960
|
+
left.ino === right.ino &&
|
|
961
|
+
left.size === right.size &&
|
|
962
|
+
left.mtimeNs === right.mtimeNs &&
|
|
963
|
+
left.ctimeNs === right.ctimeNs);
|
|
964
|
+
}
|
|
965
|
+
async function captureModelConfigFile(agentDir) {
|
|
966
|
+
const filePath = path.join(agentDir, "models.json");
|
|
967
|
+
return {
|
|
968
|
+
path: filePath,
|
|
969
|
+
version: await readModelConfigFileVersion(filePath),
|
|
970
|
+
};
|
|
971
|
+
}
|
|
934
972
|
/**
|
|
935
973
|
* Console Pi runtime — holds Chat sessions and drives prompt() with three-gate
|
|
936
974
|
* tool enforcement. Session-per-sessionId; cross-request reuse via open().
|
|
@@ -940,6 +978,8 @@ export class ConsolePiRuntime {
|
|
|
940
978
|
loader;
|
|
941
979
|
sessions = new Map();
|
|
942
980
|
modelRuntimes = new Map();
|
|
981
|
+
/** Non-sensitive models.json metadata used to avoid reparsing unchanged config. */
|
|
982
|
+
modelConfigFiles = new Map();
|
|
943
983
|
sessionManagers = new Map();
|
|
944
984
|
/** Per-session SDK service scope (resourceLoader / settingsManager) used by the live runtime snapshot. */
|
|
945
985
|
serviceScopes = new Map();
|
|
@@ -949,6 +989,8 @@ export class ConsolePiRuntime {
|
|
|
949
989
|
reloadInFlight = new Set();
|
|
950
990
|
/** Synchronous prompt ownership per session (admission before first await). */
|
|
951
991
|
promptOwners = new Set();
|
|
992
|
+
/** Steering requests accepted by the SDK but not yet consumed into Pi context. */
|
|
993
|
+
pendingSteering = new Map();
|
|
952
994
|
/** At most one detached automatic-title task per durable Console session. */
|
|
953
995
|
titleInFlight = new Set();
|
|
954
996
|
/** Composer thinking selection ("auto" = no explicit override). */
|
|
@@ -1341,6 +1383,9 @@ export class ConsolePiRuntime {
|
|
|
1341
1383
|
if (this.disposedSessions.has(sessionId))
|
|
1342
1384
|
return;
|
|
1343
1385
|
const agentDir = await safeGetAgentDir(this.bindings);
|
|
1386
|
+
// Capture before services parse models.json. A concurrent edit can therefore
|
|
1387
|
+
// cause at most one harmless extra refresh, never a missed next-turn refresh.
|
|
1388
|
+
const modelConfigFile = await captureModelConfigFile(agentDir);
|
|
1344
1389
|
const appendSystemPrompt = [
|
|
1345
1390
|
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1346
1391
|
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
@@ -1400,6 +1445,7 @@ export class ConsolePiRuntime {
|
|
|
1400
1445
|
this.sessions.set(sessionId, session);
|
|
1401
1446
|
this.sessionManagers.set(sessionId, init.sessionManager);
|
|
1402
1447
|
this.modelRuntimes.set(sessionId, modelRuntime);
|
|
1448
|
+
this.modelConfigFiles.set(sessionId, modelConfigFile);
|
|
1403
1449
|
this.sessionModels.set(sessionId, model);
|
|
1404
1450
|
this.serviceScopes.set(sessionId, {
|
|
1405
1451
|
resourceLoader: services.resourceLoader,
|
|
@@ -1420,6 +1466,7 @@ export class ConsolePiRuntime {
|
|
|
1420
1466
|
this.sessions.delete(sessionId);
|
|
1421
1467
|
this.sessionManagers.delete(sessionId);
|
|
1422
1468
|
this.modelRuntimes.delete(sessionId);
|
|
1469
|
+
this.modelConfigFiles.delete(sessionId);
|
|
1423
1470
|
this.sessionModels.delete(sessionId);
|
|
1424
1471
|
this.serviceScopes.delete(sessionId);
|
|
1425
1472
|
this.revisions.delete(sessionId);
|
|
@@ -1498,6 +1545,7 @@ export class ConsolePiRuntime {
|
|
|
1498
1545
|
this.sessions.set(sessionId, built.session);
|
|
1499
1546
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
1500
1547
|
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
1548
|
+
this.modelConfigFiles.set(sessionId, built.modelConfigFile);
|
|
1501
1549
|
this.sessionModels.set(sessionId, init.model);
|
|
1502
1550
|
this.serviceScopes.set(sessionId, built.services);
|
|
1503
1551
|
this.revisions.set(sessionId, 1);
|
|
@@ -1569,11 +1617,69 @@ export class ConsolePiRuntime {
|
|
|
1569
1617
|
if (!resolved)
|
|
1570
1618
|
throw new Error("MODEL_NOT_AVAILABLE");
|
|
1571
1619
|
await session.setModel(resolved);
|
|
1572
|
-
|
|
1573
|
-
return {
|
|
1620
|
+
const applied = {
|
|
1574
1621
|
provider: session.model?.provider ?? model.provider,
|
|
1575
1622
|
modelId: session.model?.id ?? session.model?.modelId ?? model.modelId,
|
|
1576
1623
|
};
|
|
1624
|
+
this.sessionModels.set(sessionId, applied);
|
|
1625
|
+
this.bumpRevision(sessionId);
|
|
1626
|
+
return applied;
|
|
1627
|
+
}
|
|
1628
|
+
async refreshModelConfiguration(sessionId, mode) {
|
|
1629
|
+
const session = this.sessions.get(sessionId);
|
|
1630
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1631
|
+
const configFile = this.modelConfigFiles.get(sessionId);
|
|
1632
|
+
if (!session || !modelRuntime || !configFile) {
|
|
1633
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1634
|
+
}
|
|
1635
|
+
const nextVersion = await readModelConfigFileVersion(configFile.path);
|
|
1636
|
+
if (mode === "if-changed" &&
|
|
1637
|
+
sameModelConfigFileVersion(configFile.version, nextVersion)) {
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
if (typeof modelRuntime.refresh !== "function") {
|
|
1641
|
+
throw new Error("MODEL_CONFIG_REFRESH_UNSUPPORTED");
|
|
1642
|
+
}
|
|
1643
|
+
const stored = this.sessionModels.get(sessionId);
|
|
1644
|
+
const provider = session.model?.provider ?? stored?.provider;
|
|
1645
|
+
const modelId = session.model?.id ?? session.model?.modelId ?? stored?.modelId;
|
|
1646
|
+
try {
|
|
1647
|
+
await modelRuntime.refresh({ allowNetwork: false });
|
|
1648
|
+
if (typeof modelRuntime.getError === "function" && modelRuntime.getError()) {
|
|
1649
|
+
throw new Error("MODEL_CONFIG_REFRESH_FAILED");
|
|
1650
|
+
}
|
|
1651
|
+
if (provider && modelId) {
|
|
1652
|
+
if (typeof session.setModel !== "function") {
|
|
1653
|
+
throw new Error("MODEL_CONFIG_REBIND_UNSUPPORTED");
|
|
1654
|
+
}
|
|
1655
|
+
const resolved = this.bindings.resolveModel({
|
|
1656
|
+
modelRuntime,
|
|
1657
|
+
provider,
|
|
1658
|
+
modelId,
|
|
1659
|
+
});
|
|
1660
|
+
if (!resolved) {
|
|
1661
|
+
throw new Error("MODEL_NOT_AVAILABLE_AFTER_CONFIG_REFRESH");
|
|
1662
|
+
}
|
|
1663
|
+
await session.setModel(resolved);
|
|
1664
|
+
this.sessionModels.set(sessionId, { provider, modelId });
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
catch (error) {
|
|
1668
|
+
if (error instanceof Error &&
|
|
1669
|
+
(error.message === "MODEL_CONFIG_REBIND_UNSUPPORTED" ||
|
|
1670
|
+
error.message === "MODEL_NOT_AVAILABLE_AFTER_CONFIG_REFRESH")) {
|
|
1671
|
+
throw error;
|
|
1672
|
+
}
|
|
1673
|
+
// Do not surface SDK/config error text: it may contain credential material.
|
|
1674
|
+
throw new Error("MODEL_CONFIG_REFRESH_FAILED");
|
|
1675
|
+
}
|
|
1676
|
+
if (this.disposedSessions.has(sessionId) ||
|
|
1677
|
+
this.sessions.get(sessionId) !== session) {
|
|
1678
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1679
|
+
}
|
|
1680
|
+
configFile.version = nextVersion;
|
|
1681
|
+
this.bumpRevision(sessionId);
|
|
1682
|
+
return true;
|
|
1577
1683
|
}
|
|
1578
1684
|
/**
|
|
1579
1685
|
* Apply a thinking-level selection. `auto` records the selection WITHOUT
|
|
@@ -1696,6 +1802,8 @@ export class ConsolePiRuntime {
|
|
|
1696
1802
|
};
|
|
1697
1803
|
}
|
|
1698
1804
|
const busyReasons = [];
|
|
1805
|
+
if (this.promptOwners.has(sessionId))
|
|
1806
|
+
busyReasons.push("prompt in flight");
|
|
1699
1807
|
if (session.isStreaming === true || session.isIdle === false)
|
|
1700
1808
|
busyReasons.push("streaming");
|
|
1701
1809
|
if (session.isCompacting === true)
|
|
@@ -1727,6 +1835,7 @@ export class ConsolePiRuntime {
|
|
|
1727
1835
|
const services = this.serviceScopes.get(sessionId);
|
|
1728
1836
|
if (services)
|
|
1729
1837
|
this.applySessionCompactionOverride(sessionId, services, session);
|
|
1838
|
+
await this.refreshModelConfiguration(sessionId, "force");
|
|
1730
1839
|
}
|
|
1731
1840
|
catch (error) {
|
|
1732
1841
|
return {
|
|
@@ -1738,7 +1847,6 @@ export class ConsolePiRuntime {
|
|
|
1738
1847
|
finally {
|
|
1739
1848
|
this.reloadInFlight.delete(sessionId);
|
|
1740
1849
|
}
|
|
1741
|
-
this.bumpRevision(sessionId);
|
|
1742
1850
|
// Reload replaces the effective resource/session context. Persisted Goal
|
|
1743
1851
|
// remains visible, but continuation authorization must be renewed by user.
|
|
1744
1852
|
this.options.goalRegistry?.disarm(sessionId);
|
|
@@ -2257,6 +2365,7 @@ export class ConsolePiRuntime {
|
|
|
2257
2365
|
this.sessions.set(sessionId, session);
|
|
2258
2366
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
2259
2367
|
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
2368
|
+
this.modelConfigFiles.set(sessionId, built.modelConfigFile);
|
|
2260
2369
|
this.sessionModels.set(sessionId, init.model);
|
|
2261
2370
|
this.serviceScopes.set(sessionId, built.services);
|
|
2262
2371
|
this.revisions.set(sessionId, 1);
|
|
@@ -2310,6 +2419,7 @@ export class ConsolePiRuntime {
|
|
|
2310
2419
|
*/
|
|
2311
2420
|
async buildSessionWithServices(init) {
|
|
2312
2421
|
const agentDir = await safeGetAgentDir(this.bindings);
|
|
2422
|
+
const modelConfigFile = await captureModelConfigFile(agentDir);
|
|
2313
2423
|
const appendSystemPrompt = [
|
|
2314
2424
|
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
2315
2425
|
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
@@ -2343,6 +2453,7 @@ export class ConsolePiRuntime {
|
|
|
2343
2453
|
return {
|
|
2344
2454
|
session,
|
|
2345
2455
|
modelRuntime,
|
|
2456
|
+
modelConfigFile,
|
|
2346
2457
|
services: {
|
|
2347
2458
|
resourceLoader: services.resourceLoader,
|
|
2348
2459
|
settingsManager: services.settingsManager,
|
|
@@ -2354,14 +2465,31 @@ export class ConsolePiRuntime {
|
|
|
2354
2465
|
return this.sessions.get(sessionId);
|
|
2355
2466
|
}
|
|
2356
2467
|
/** Inject one queued message into the currently running Pi turn. */
|
|
2357
|
-
async steer(sessionId, text, images) {
|
|
2468
|
+
async steer(sessionId, clientRequestId, text, images) {
|
|
2358
2469
|
const session = this.sessions.get(sessionId);
|
|
2359
2470
|
if (!session || !this.promptOwners.has(sessionId) || session.isStreaming === false) {
|
|
2360
2471
|
throw new Error("CHAT_TURN_NOT_ACTIVE");
|
|
2361
2472
|
}
|
|
2362
2473
|
if (typeof session.steer !== "function")
|
|
2363
2474
|
throw new Error("CHAT_STEER_UNAVAILABLE");
|
|
2364
|
-
|
|
2475
|
+
const pending = this.pendingSteering.get(sessionId) ?? [];
|
|
2476
|
+
const identity = { clientRequestId, text };
|
|
2477
|
+
pending.push(identity);
|
|
2478
|
+
this.pendingSteering.set(sessionId, pending);
|
|
2479
|
+
try {
|
|
2480
|
+
await session.steer(text, images);
|
|
2481
|
+
}
|
|
2482
|
+
catch (error) {
|
|
2483
|
+
const current = this.pendingSteering.get(sessionId);
|
|
2484
|
+
if (current) {
|
|
2485
|
+
const index = current.indexOf(identity);
|
|
2486
|
+
if (index >= 0)
|
|
2487
|
+
current.splice(index, 1);
|
|
2488
|
+
if (current.length === 0)
|
|
2489
|
+
this.pendingSteering.delete(sessionId);
|
|
2490
|
+
}
|
|
2491
|
+
throw error;
|
|
2492
|
+
}
|
|
2365
2493
|
}
|
|
2366
2494
|
hasSession(sessionId) {
|
|
2367
2495
|
return this.sessions.has(sessionId);
|
|
@@ -2397,12 +2525,13 @@ export class ConsolePiRuntime {
|
|
|
2397
2525
|
*/
|
|
2398
2526
|
async prompt(sessionId, text, onEvent, options) {
|
|
2399
2527
|
// Synchronous admission fence — before any await.
|
|
2400
|
-
if (this.promptOwners.has(sessionId)) {
|
|
2528
|
+
if (this.promptOwners.has(sessionId) || this.reloadInFlight.has(sessionId)) {
|
|
2401
2529
|
throw new Error("CHAT_TURN_ACTIVE");
|
|
2402
2530
|
}
|
|
2403
2531
|
this.promptOwners.add(sessionId);
|
|
2404
2532
|
let unsub;
|
|
2405
2533
|
let abortPromise;
|
|
2534
|
+
let initialUserMessageStarted = false;
|
|
2406
2535
|
const onAbort = () => {
|
|
2407
2536
|
// M1 T08 / AC-R3-003: Stop must abort only the current turn, NEVER
|
|
2408
2537
|
// dispose the session. Await session.abort() so ownership settles
|
|
@@ -2459,6 +2588,11 @@ export class ConsolePiRuntime {
|
|
|
2459
2588
|
if (options?.signal?.aborted) {
|
|
2460
2589
|
return;
|
|
2461
2590
|
}
|
|
2591
|
+
await this.refreshModelConfiguration(sessionId, "if-changed");
|
|
2592
|
+
// Stop may arrive while stat/refresh/rebind is in flight.
|
|
2593
|
+
if (options?.signal?.aborted) {
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2462
2596
|
const emitContextUsage = () => {
|
|
2463
2597
|
try {
|
|
2464
2598
|
const contextUsage = session.getSessionStats?.()?.contextUsage;
|
|
@@ -2500,6 +2634,27 @@ export class ConsolePiRuntime {
|
|
|
2500
2634
|
};
|
|
2501
2635
|
unsub = session.subscribe((event) => {
|
|
2502
2636
|
const observedAt = Date.now();
|
|
2637
|
+
if (event.type === "message_start" && isUserSdkMessage(event.message)) {
|
|
2638
|
+
if (!initialUserMessageStarted) {
|
|
2639
|
+
// The first user message_start belongs to session.prompt(text), not
|
|
2640
|
+
// to a steering request that may have raced with prompt startup.
|
|
2641
|
+
initialUserMessageStarted = true;
|
|
2642
|
+
}
|
|
2643
|
+
else {
|
|
2644
|
+
const pending = this.pendingSteering.get(sessionId);
|
|
2645
|
+
const accepted = pending?.shift();
|
|
2646
|
+
if (pending?.length === 0)
|
|
2647
|
+
this.pendingSteering.delete(sessionId);
|
|
2648
|
+
if (accepted) {
|
|
2649
|
+
onEvent({
|
|
2650
|
+
type: "steering_accepted",
|
|
2651
|
+
sessionId,
|
|
2652
|
+
clientRequestId: accepted.clientRequestId,
|
|
2653
|
+
text: accepted.text,
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2503
2658
|
if (event.type === "compaction_start") {
|
|
2504
2659
|
activeCompaction = {
|
|
2505
2660
|
compactionId: newCompactionId(sessionId),
|
|
@@ -2638,6 +2793,10 @@ export class ConsolePiRuntime {
|
|
|
2638
2793
|
finally {
|
|
2639
2794
|
if (this.options.questionRegistry)
|
|
2640
2795
|
this.options.questionRegistry.clearTurn(sessionId);
|
|
2796
|
+
// Unconsumed durable DeferredTurns remain dispatching and are recovered by
|
|
2797
|
+
// the route layer after this active Turn closes. Never carry their volatile
|
|
2798
|
+
// identities into a later prompt.
|
|
2799
|
+
this.pendingSteering.delete(sessionId);
|
|
2641
2800
|
this.promptOwners.delete(sessionId);
|
|
2642
2801
|
}
|
|
2643
2802
|
}
|
|
@@ -2759,11 +2918,13 @@ export class ConsolePiRuntime {
|
|
|
2759
2918
|
this.sessionManagers.delete(sessionId);
|
|
2760
2919
|
this.mainlineLeaves.delete(sessionId);
|
|
2761
2920
|
this.modelRuntimes.delete(sessionId);
|
|
2921
|
+
this.modelConfigFiles.delete(sessionId);
|
|
2762
2922
|
this.sessionModels.delete(sessionId);
|
|
2763
2923
|
this.serviceScopes.delete(sessionId);
|
|
2764
2924
|
this.revisions.delete(sessionId);
|
|
2765
2925
|
this.thinkingSelections.delete(sessionId);
|
|
2766
2926
|
this.reloadInFlight.delete(sessionId);
|
|
2927
|
+
this.pendingSteering.delete(sessionId);
|
|
2767
2928
|
}
|
|
2768
2929
|
disposeAll() {
|
|
2769
2930
|
for (const id of [...this.sessions.keys()])
|
|
@@ -2796,6 +2957,11 @@ function mapAssistantMessageEvent(type, sessionId, message) {
|
|
|
2796
2957
|
...(content.length > 0 ? { content } : {}),
|
|
2797
2958
|
};
|
|
2798
2959
|
}
|
|
2960
|
+
function isUserSdkMessage(message) {
|
|
2961
|
+
return Boolean(message &&
|
|
2962
|
+
typeof message === "object" &&
|
|
2963
|
+
message.role === "user");
|
|
2964
|
+
}
|
|
2799
2965
|
function mapSdkEvent(sessionId, event, compactionId) {
|
|
2800
2966
|
switch (event.type) {
|
|
2801
2967
|
case "agent_start":
|
|
@@ -9,6 +9,9 @@ const TREE_LIMIT = 500;
|
|
|
9
9
|
export const TEXT_PREVIEW_LIMIT = 512 * 1024;
|
|
10
10
|
/** Full-file ceiling for the sandboxed HTML preview route and its assets. */
|
|
11
11
|
export const HTML_PREVIEW_ASSET_LIMIT = 20 * 1024 * 1024;
|
|
12
|
+
/** Self-contained HTML exports commonly embed data and can be much larger than
|
|
13
|
+
* an individual image/script asset. Keep a separate, still-bounded ceiling. */
|
|
14
|
+
export const HTML_PREVIEW_DOCUMENT_LIMIT = 64 * 1024 * 1024;
|
|
12
15
|
export const REPO_HTML_PREVIEW_ROUTE_PREFIX = "/api/operator/v1/chat/repo/html/";
|
|
13
16
|
const SKIP_DIRS = new Set([
|
|
14
17
|
".git",
|
|
@@ -190,8 +193,12 @@ export async function readRepoHtmlAsset(repoRoot, rawPath) {
|
|
|
190
193
|
const info = await handle.stat();
|
|
191
194
|
if (!info.isFile())
|
|
192
195
|
throw new RepoBrowserError(400, "NOT_A_FILE", "path is not a regular file");
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
const contentType = repoHtmlAssetContentType(guard.repoRelative);
|
|
197
|
+
const limit = contentType.startsWith("text/html")
|
|
198
|
+
? HTML_PREVIEW_DOCUMENT_LIMIT
|
|
199
|
+
: HTML_PREVIEW_ASSET_LIMIT;
|
|
200
|
+
if (info.size > limit)
|
|
201
|
+
throw new RepoBrowserError(413, "TOO_LARGE", `file exceeds ${limit} byte HTML preview limit`);
|
|
195
202
|
const rechecked = await guardReadPath(repoRoot, rawPath);
|
|
196
203
|
if (!rechecked.ok)
|
|
197
204
|
throw new RepoBrowserError(403, guardedCode(rechecked.code), "file path changed while it was being opened");
|
|
@@ -202,7 +209,7 @@ export async function readRepoHtmlAsset(repoRoot, rawPath) {
|
|
|
202
209
|
throw new RepoBrowserError(403, "OUT_OF_REPO", "file identity changed while it was being opened");
|
|
203
210
|
return {
|
|
204
211
|
body: await handle.readFile(),
|
|
205
|
-
contentType
|
|
212
|
+
contentType,
|
|
206
213
|
bytes: info.size,
|
|
207
214
|
};
|
|
208
215
|
}
|
|
@@ -1065,20 +1065,10 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
|
|
|
1065
1065
|
}
|
|
1066
1066
|
const item = claim.deferredTurn;
|
|
1067
1067
|
try {
|
|
1068
|
-
await deps.runtime.steer(sessionId, item.text, item.images);
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
});
|
|
1073
|
-
deps.events.append(sessionId, active.turnId, {
|
|
1074
|
-
kind: "user_message",
|
|
1075
|
-
data: {
|
|
1076
|
-
clientRequestId: item.clientRequestId,
|
|
1077
|
-
message: { id: userMessage.id, text: userMessage.text, at: userMessage.at },
|
|
1078
|
-
},
|
|
1079
|
-
});
|
|
1080
|
-
await deps.store.clearDeferredTurnForRequest(sessionId, item.clientRequestId);
|
|
1081
|
-
await emitDeferredTurnUpdate(deps, sessionId, active.turnId, undefined, item.revision + 2);
|
|
1068
|
+
await deps.runtime.steer(sessionId, item.clientRequestId, item.text, item.images);
|
|
1069
|
+
// session.steer() only acknowledges queueing. Keep the durable item in
|
|
1070
|
+
// dispatching until Pi emits the corresponding user message_start.
|
|
1071
|
+
await emitDeferredTurnUpdate(deps, sessionId, active.turnId, item, item.revision);
|
|
1082
1072
|
sendJson(res, 200, {
|
|
1083
1073
|
ok: true,
|
|
1084
1074
|
accepted: true,
|
|
@@ -1102,6 +1092,21 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
|
|
|
1102
1092
|
});
|
|
1103
1093
|
}
|
|
1104
1094
|
}
|
|
1095
|
+
async function persistAcceptedSteering(deps, sessionId, turnId, userMessage, clientRequestId) {
|
|
1096
|
+
deps.events.append(sessionId, turnId, {
|
|
1097
|
+
kind: "user_message",
|
|
1098
|
+
data: {
|
|
1099
|
+
clientRequestId,
|
|
1100
|
+
steering: true,
|
|
1101
|
+
message: {
|
|
1102
|
+
id: userMessage.id,
|
|
1103
|
+
text: userMessage.text,
|
|
1104
|
+
at: userMessage.at,
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1107
|
+
});
|
|
1108
|
+
await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
|
|
1109
|
+
}
|
|
1105
1110
|
/** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
|
|
1106
1111
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
1107
1112
|
* outcome WITHOUT the full message payload, so the UI can poll it on an
|
|
@@ -1661,6 +1666,9 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1661
1666
|
if (!res.writableEnded)
|
|
1662
1667
|
writeChatSse(res, persisted);
|
|
1663
1668
|
},
|
|
1669
|
+
onSteeringMessagePersisted: async (_record, userMessage, accepted) => {
|
|
1670
|
+
await persistAcceptedSteering(deps, sessionId, turnId, userMessage, accepted.clientRequestId);
|
|
1671
|
+
},
|
|
1664
1672
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1665
1673
|
sessionId,
|
|
1666
1674
|
turnId,
|
|
@@ -1971,6 +1979,9 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1971
1979
|
if (placeholder)
|
|
1972
1980
|
emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
|
|
1973
1981
|
},
|
|
1982
|
+
onSteeringMessagePersisted: async (_record, userMessage, accepted) => {
|
|
1983
|
+
await persistAcceptedSteering(deps, sessionId, turn.turnId, userMessage, accepted.clientRequestId);
|
|
1984
|
+
},
|
|
1974
1985
|
persistUserMessage: trustedPayload?.persistUserMessage,
|
|
1975
1986
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1976
1987
|
sessionId,
|
|
@@ -2646,13 +2657,13 @@ async function handleRepoHtmlPreview(res, deps, rawPath) {
|
|
|
2646
2657
|
"Cache-Control": "no-store",
|
|
2647
2658
|
"X-Content-Type-Options": "nosniff",
|
|
2648
2659
|
"Referrer-Policy": "no-referrer",
|
|
2649
|
-
//
|
|
2650
|
-
// sandbox
|
|
2651
|
-
//
|
|
2660
|
+
// Project scripts may drive tabs and other local interactions, but the
|
|
2661
|
+
// opaque sandbox still withholds same-origin access, forms, popups and
|
|
2662
|
+
// top-level navigation. Network requests remain disabled below.
|
|
2652
2663
|
"Content-Security-Policy": [
|
|
2653
|
-
"sandbox",
|
|
2664
|
+
"sandbox allow-scripts",
|
|
2654
2665
|
"default-src 'none'",
|
|
2655
|
-
|
|
2666
|
+
`script-src 'unsafe-inline' ${origin}`,
|
|
2656
2667
|
"object-src 'none'",
|
|
2657
2668
|
"frame-src 'none'",
|
|
2658
2669
|
"form-action 'none'",
|