@tea-agent/loop-agent 0.34.3 → 0.34.5

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 (47) hide show
  1. package/AGENTS.md +8 -3
  2. package/CHANGELOG.md +66 -22
  3. package/README.md +2 -2
  4. package/dist/shared/operator/capabilities.js +0 -7
  5. package/dist/worker/cli.js +21 -27
  6. package/dist/worker/console/app-data.js +2 -0
  7. package/dist/worker/console/chat/chat-event-store.js +134 -4
  8. package/dist/worker/console/chat/pi-runtime.js +308 -63
  9. package/dist/worker/console/chat/resource-preferences-store.js +152 -0
  10. package/dist/worker/console/chat/routes.js +425 -19
  11. package/dist/worker/console/chat/shortcuts.js +208 -9
  12. package/dist/worker/console/chat/tool-preview.js +162 -5
  13. package/dist/worker/console/chat/turn-execution-registry.js +82 -0
  14. package/dist/worker/console/dag-execution-receipt.js +14 -1
  15. package/dist/worker/console/doctor.js +1 -1
  16. package/dist/worker/console/index.js +1 -0
  17. package/dist/worker/console/open-browser.js +134 -0
  18. package/dist/worker/console/recovery-cta.js +1 -1
  19. package/dist/worker/console/server.js +5 -1
  20. package/dist/worker/console/static/assets/index-qpkysQYW.css +1 -0
  21. package/dist/worker/console/static/assets/index-y980PqtP.js +56 -0
  22. package/dist/worker/console/static/index.html +2 -2
  23. package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
  24. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +1 -3
  25. package/dist/worker/console/static-src/operator-chat/format.js +2 -2
  26. package/dist/worker/console/static-src/operator-chat/refs.js +24 -0
  27. package/dist/worker/console/static-src/operator-chat/resource-auto-invocation.js +91 -0
  28. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +690 -0
  29. package/dist/worker/console/static-src/operator-chat/turn-submission.js +158 -0
  30. package/dist/worker/console/static-src/operator-chat/useChatStream.js +535 -86
  31. package/dist/worker/console/static-src/operator-chat/useChatThread.js +11 -5
  32. package/dist/worker/console/static-src/operator-chat/useComposer.js +81 -3
  33. package/dist/workflows/dag/init-hybrid.js +2 -0
  34. package/docs/architecture/evolution.md +1 -1
  35. package/docs/architecture/system-overview.md +1 -1
  36. package/docs/architecture/worker-and-feature.md +1 -1
  37. package/docs/operations/local-development-environment.md +4 -2
  38. package/docs/templates/branch-merge-report.md +9 -0
  39. package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
  40. package/docs/templates/evaluation/agents-map-verbose-v0.md +2 -2
  41. package/docs/templates/init-managed-agents.md +2 -2
  42. package/package.json +6 -2
  43. package/skills/agent-worker/SKILL.md +1 -1
  44. package/skills/agent-worker/references/agent-worker-operator.md +2 -2
  45. package/skills/loop-agent/references/command-reference.md +2 -3
  46. package/dist/worker/console/static/assets/index-BQkhJpV8.css +0 -1
  47. package/dist/worker/console/static/assets/index-BpuHmlSP.js +0 -29
@@ -6,8 +6,8 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <link rel="stylesheet" href="/inspect/operator-chrome.css" />
8
8
  <title>Loop 操作台 · Operator Console</title>
9
- <script type="module" crossorigin src="/assets/index-BpuHmlSP.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-BQkhJpV8.css">
9
+ <script type="module" crossorigin src="/assets/index-y980PqtP.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-qpkysQYW.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure security helpers for Operator Chat Markdown (UI-07).
3
+ * Kept free of JSX so root `tsc` (no jsx) and vitest can import them.
4
+ */
5
+ const DANGEROUS_PROTOCOLS = /^(javascript|vbscript|data):/i;
6
+ export function isSafeMarkdownHref(href) {
7
+ if (!href)
8
+ return false;
9
+ const trimmed = href.trim();
10
+ if (!trimmed)
11
+ return false;
12
+ if (DANGEROUS_PROTOCOLS.test(trimmed))
13
+ return false;
14
+ return true;
15
+ }
16
+ /** Controlled local/session image paths may load; remote/unknown never auto-fetch. */
17
+ export function isAllowedMarkdownImageSrc(src) {
18
+ if (!src)
19
+ return false;
20
+ const trimmed = src.trim();
21
+ if (!trimmed)
22
+ return false;
23
+ if (DANGEROUS_PROTOCOLS.test(trimmed))
24
+ return false;
25
+ if (trimmed.startsWith("blob:") || trimmed.startsWith("data:image/")) {
26
+ return false;
27
+ }
28
+ if (/^https?:\/\//i.test(trimmed))
29
+ return false;
30
+ if (trimmed.startsWith("/") ||
31
+ trimmed.startsWith("./") ||
32
+ trimmed.startsWith("../")) {
33
+ return true;
34
+ }
35
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed))
36
+ return true;
37
+ return false;
38
+ }
@@ -9,7 +9,7 @@ import { requestId } from "./format.js";
9
9
  * original request would have. `assistantId` is the in-flight assistant
10
10
  * message id to patch for streaming text; reconnect creates one if none. */
11
11
  export function createChatSseEventApplier(deps) {
12
- const { refs, onReconcile, setError, setProcessOpen, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
12
+ const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
13
13
  return (eventName, data, assistantId, options) => {
14
14
  if (options?.generation !== undefined &&
15
15
  refs.sessionGenerationRef.current !== options.generation)
@@ -275,8 +275,6 @@ export function createChatSseEventApplier(deps) {
275
275
  ? payload.resultPreview
276
276
  : "{}";
277
277
  const now = Date.now();
278
- if (isError)
279
- setProcessOpen(true);
280
278
  setToolCalls((prev) => {
281
279
  const exists = prev.find((t) => t.id === id);
282
280
  if (exists) {
@@ -1,4 +1,4 @@
1
- import { truncateToolResult } from "../../chat/tool-preview.js";
1
+ import { extractToolResultText, truncateToolResult } from "../../chat/tool-preview.js";
2
2
  export function confirmationToken() {
3
3
  const w = window;
4
4
  return w.__CONSOLE_CONFIRMATION_TOKEN__ ?? "";
@@ -41,7 +41,7 @@ export function fmtDaySep(ts) {
41
41
  }
42
42
  export function resultPreview(result) {
43
43
  try {
44
- const s = typeof result === "string" ? result : JSON.stringify(result, null, 2);
44
+ const s = extractToolResultText(result);
45
45
  return truncateToolResult(s).text;
46
46
  }
47
47
  catch {
@@ -14,6 +14,14 @@ export function useChatRefs() {
14
14
  const turnAssistantIdsRef = useRef(new Map());
15
15
  const reconnectAbortRef = useRef(null);
16
16
  const activeTurnIdRef = useRef(null);
17
+ const submitInFlightRef = useRef(false);
18
+ const submitGenerationRef = useRef(0);
19
+ const pendingSubmissionRef = useRef(null);
20
+ const stopInFlightRef = useRef(false);
21
+ const stopRequestedRef = useRef(false);
22
+ const stoppingTurnIdRef = useRef(null);
23
+ const abortPostedTurnIdsRef = useRef(new Set());
24
+ const releasedClientRequestIdsRef = useRef(new Set());
17
25
  const usageResponseKeysRef = useRef(new Set());
18
26
  const isComposingRef = useRef(false);
19
27
  const compositionEndedAtRef = useRef(0);
@@ -34,6 +42,14 @@ export function useChatRefs() {
34
42
  turnAssistantIdsRef,
35
43
  reconnectAbortRef,
36
44
  activeTurnIdRef,
45
+ submitInFlightRef,
46
+ submitGenerationRef,
47
+ pendingSubmissionRef,
48
+ stopInFlightRef,
49
+ stopRequestedRef,
50
+ stoppingTurnIdRef,
51
+ abortPostedTurnIdsRef,
52
+ releasedClientRequestIdsRef,
37
53
  usageResponseKeysRef,
38
54
  isComposingRef,
39
55
  compositionEndedAtRef,
@@ -52,6 +68,14 @@ export function useChatRefs() {
52
68
  turnAssistantIdsRef,
53
69
  reconnectAbortRef,
54
70
  activeTurnIdRef,
71
+ submitInFlightRef,
72
+ submitGenerationRef,
73
+ pendingSubmissionRef,
74
+ stopInFlightRef,
75
+ stopRequestedRef,
76
+ stoppingTurnIdRef,
77
+ abortPostedTurnIdsRef,
78
+ releasedClientRequestIdsRef,
55
79
  usageResponseKeysRef,
56
80
  isComposingRef,
57
81
  compositionEndedAtRef,
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Operator Chat — Skills panel auto-invocation mutation client.
3
+ *
4
+ * Real PATCH only (contract mockApi.strategy = not-needed). Fail closed on
5
+ * write errors; adopt returned snapshot when reloadApplied; surface partial
6
+ * busy (preference saved, reload pending) without aborting the turn.
7
+ */
8
+ import { parseRuntimeSnapshotError } from "./runtime-snapshot-store.js";
9
+ function autoInvocationUrl(origin, sessionId, resourceId) {
10
+ return `${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/skills/${encodeURIComponent(resourceId)}/auto-invocation`;
11
+ }
12
+ /**
13
+ * PATCH skill auto-invocation preference for the current session's repo.
14
+ * Never comments out or replaces the real request path.
15
+ */
16
+ export async function patchSkillAutoInvocation(input) {
17
+ const fetchImpl = input.fetchImpl ?? fetch;
18
+ try {
19
+ const res = await fetchImpl(autoInvocationUrl(input.origin, input.sessionId, input.resourceId), {
20
+ method: "PATCH",
21
+ credentials: "include",
22
+ headers: {
23
+ "content-type": "application/json",
24
+ "x-loop-console-confirmation": input.confirmationToken,
25
+ },
26
+ body: JSON.stringify({
27
+ autoInvocationEnabled: input.autoInvocationEnabled,
28
+ }),
29
+ });
30
+ const body = (await res.json().catch(() => ({})));
31
+ if (!res.ok || body.ok === false) {
32
+ return {
33
+ ok: false,
34
+ error: {
35
+ ...parseRuntimeSnapshotError(body),
36
+ status: res.status,
37
+ },
38
+ };
39
+ }
40
+ const preferenceSaved = body.preferenceSaved === true;
41
+ const reloadApplied = body.reloadApplied === true;
42
+ if (!preferenceSaved) {
43
+ return {
44
+ ok: false,
45
+ error: {
46
+ status: res.status,
47
+ code: "PI_RESOURCE_PREFERENCES_WRITE_FAILED",
48
+ message: "preference was not saved",
49
+ },
50
+ };
51
+ }
52
+ if (!reloadApplied) {
53
+ const code = body.error?.code ?? "PI_SESSION_BUSY";
54
+ const message = body.error?.message ??
55
+ "preference saved; current session is busy and was not reloaded";
56
+ return {
57
+ ok: true,
58
+ preferenceSaved: true,
59
+ reloadApplied: false,
60
+ ...(body.snapshot ? { snapshot: body.snapshot } : {}),
61
+ partialBusy: { code, message },
62
+ };
63
+ }
64
+ return {
65
+ ok: true,
66
+ preferenceSaved: true,
67
+ reloadApplied: true,
68
+ ...(body.snapshot ? { snapshot: body.snapshot } : {}),
69
+ };
70
+ }
71
+ catch (error) {
72
+ return {
73
+ ok: false,
74
+ error: {
75
+ message: error instanceof Error ? error.message : String(error),
76
+ },
77
+ };
78
+ }
79
+ }
80
+ export function autoInvocationStatusCopy(skill) {
81
+ if (skill.autoInvocationEnabled) {
82
+ return {
83
+ badge: "允许自动使用",
84
+ help: "模型可自动选择该技能,也可通过 /skill:<name> 手动调用。",
85
+ };
86
+ }
87
+ return {
88
+ badge: "已隐藏自动使用",
89
+ help: `已从模型自动技能列表隐藏,仍可通过 /skill:${skill.name} 手动调用。`,
90
+ };
91
+ }