@piagent/platform 1.22.1 → 1.23.0

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 (78) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -1
  3. package/README.vi.md +1 -1
  4. package/docs/command-reference-vietnamese.md +2 -2
  5. package/docs/distribution-standard.md +6 -6
  6. package/docs/languages.json +2 -2
  7. package/docs/operator-manual-vietnamese.md +7 -7
  8. package/docs/publishing-for-teams.md +1 -0
  9. package/docs/release-install-policy.md +2 -2
  10. package/docs/team-onboarding.md +2 -2
  11. package/docs-site/architecture.html +1 -1
  12. package/docs-site/benchmark.html +1 -1
  13. package/docs-site/commands.html +1 -1
  14. package/docs-site/content/en/index.html +4 -4
  15. package/docs-site/content/en/whats-new.html +8 -0
  16. package/docs-site/content/index.html +4 -4
  17. package/docs-site/content/whats-new.html +8 -0
  18. package/docs-site/context-engine.html +1 -1
  19. package/docs-site/ecosystem.html +1 -1
  20. package/docs-site/en/architecture.html +1 -1
  21. package/docs-site/en/benchmark.html +1 -1
  22. package/docs-site/en/commands.html +1 -1
  23. package/docs-site/en/context-engine.html +1 -1
  24. package/docs-site/en/ecosystem.html +1 -1
  25. package/docs-site/en/index.html +5 -5
  26. package/docs-site/en/mcp-approval.html +1 -1
  27. package/docs-site/en/mcp-auth.html +1 -1
  28. package/docs-site/en/mcp-servers.html +1 -1
  29. package/docs-site/en/mcp.html +1 -1
  30. package/docs-site/en/permissions.html +1 -1
  31. package/docs-site/en/profiles.html +1 -1
  32. package/docs-site/en/quickstart.html +1 -1
  33. package/docs-site/en/runtime.html +1 -1
  34. package/docs-site/en/scope.html +1 -1
  35. package/docs-site/en/security.html +1 -1
  36. package/docs-site/en/team.html +1 -1
  37. package/docs-site/en/whats-new.html +9 -1
  38. package/docs-site/en/windows.html +1 -1
  39. package/docs-site/en/workflows.html +1 -1
  40. package/docs-site/index.html +5 -5
  41. package/docs-site/mcp-approval.html +1 -1
  42. package/docs-site/mcp-auth.html +1 -1
  43. package/docs-site/mcp-servers.html +1 -1
  44. package/docs-site/mcp.html +1 -1
  45. package/docs-site/permissions.html +1 -1
  46. package/docs-site/profiles.html +1 -1
  47. package/docs-site/quickstart.html +1 -1
  48. package/docs-site/runtime.html +1 -1
  49. package/docs-site/scope.html +1 -1
  50. package/docs-site/security.html +1 -1
  51. package/docs-site/team.html +1 -1
  52. package/docs-site/whats-new.html +9 -1
  53. package/docs-site/windows.html +1 -1
  54. package/docs-site/workflows.html +1 -1
  55. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  56. package/package.json +1 -1
  57. package/packages/piagent-core/managed/fast-mode.mjs +66 -0
  58. package/packages/piagent-core/managed/helper-run.mjs +2 -1
  59. package/packages/piagent-core/managed/request-stream.mjs +10 -2
  60. package/packages/piagent-core/managed/session.mjs +8 -1
  61. package/packages/piagent-core/managed/tool-failures.mjs +91 -0
  62. package/packages/piagent-core/package.json +1 -1
  63. package/packages/piagent-core/runtime/managed-failure.mjs +3 -0
  64. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-EmQ_ohQe.js → MarkdownMessage-lWHlCJq4.js} +1 -1
  65. package/packages/piagent-webui/dist/client/assets/index-CDVdO4C7.js +231 -0
  66. package/packages/piagent-webui/dist/client/index.html +1 -1
  67. package/packages/piagent-webui/gateway/managed-gateway.mjs +4 -2
  68. package/packages/piagent-webui/gateway/managed-projection.ts +3 -1
  69. package/packages/piagent-webui/gateway/runtime-session-controls.ts +9 -0
  70. package/packages/piagent-webui/gateway/session-catalog.ts +3 -2
  71. package/packages/piagent-webui/gateway/session-command-controller.ts +6 -3
  72. package/packages/piagent-webui/gateway/session-command-store.ts +3 -3
  73. package/packages/piagent-webui/gateway/session-effective-options.ts +6 -1
  74. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +9 -9
  75. package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
  76. package/schemas/piagent-webui/session-command-v1.schema.json +20 -5
  77. package/scripts/company-terminal.mjs +22 -0
  78. package/packages/piagent-webui/dist/client/assets/index-Posch2XH.js +0 -231
@@ -7,7 +7,7 @@
7
7
  <meta name="csp-nonce" content="__PIAGENT_CSP_NONCE__" />
8
8
  <meta name="piagent-webui-mode" content="__PIAGENT_WEBUI_MODE__" />
9
9
  <title>Piagent WebUI</title>
10
- <script type="module" crossorigin src="/assets/index-Posch2XH.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-CDVdO4C7.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-ZLemRWyJ.css">
12
12
  </head>
13
13
  <body>
@@ -1,4 +1,4 @@
1
- import { managedThinkingLevels } from '../../piagent-core/managed/capabilities.mjs';
1
+ import { managedThinkingLevels, nearestLevel } from '../../piagent-core/managed/capabilities.mjs';
2
2
  import { ManagedBrokerClient } from '../../piagent-core/managed/broker-client.mjs';
3
3
  import { ManagedSession } from '../../piagent-core/managed/session.mjs';
4
4
  import fs from 'node:fs';
@@ -30,7 +30,9 @@ export async function startManagedGateway({ config, configPath, cwd, agentDir, p
30
30
  try {
31
31
  runtime=await ManagedSession.create({sdkRoot:config.sdk_root,cwd:info.cwd,origin:config.origin,broker,agentDir,
32
32
  sessionManager:manager??host.SessionManager.open(info.path),protectedRoots:[configPath,agentDir],renewBroker:newBroker,bindingRevision});
33
- if(initial) runtime.session.setThinkingLevel(initial.thinkingLevel);
33
+ // The page may still offer the levels of the Harness before an administrator
34
+ // changed it: the conversation takes the nearest level the Harness allows now.
35
+ if(initial) runtime.session.setThinkingLevel(nearestLevel(initial.thinkingLevel,managedThinkingLevels(runtime.manifest,runtime.modelRuntime,runtime.session.model)));
34
36
  await runtime.session.bindExtensions({mode:'rpc',uiContext:rpcUiContext()});
35
37
  return {session:runtime.session,dispose:()=>runtime.dispose()};
36
38
  } catch(error) { if(runtime) await runtime.dispose(); else await broker.dispose(); throw error; }
@@ -26,7 +26,9 @@ export function managedProjection(context: any, entries: any[]) {
26
26
  const maximum = Number.isInteger(counter?.maximum) && counter.maximum >= 0 && counter.maximum <= 4 ? counter.maximum : 2;
27
27
  // The member's access choice: "trusted-full-access" is Bypass.
28
28
  const permission = last('agent-watch-permission')?.mode === 'trusted-full-access' ? 'trusted-full-access' : 'workspace-write';
29
- return {modelLabel:'agent-watch-auto', managedPermission: permission, managedHelpers:{active:Number.isInteger(counter?.active) && counter.active>=0 && counter.active<=maximum ? counter.active : 0, maximum},
29
+ // Fast mode, only where the conversation's Harness allows it (fast-mode.mjs).
30
+ const fast = last('agent-watch-fast');
31
+ return {modelLabel:'agent-watch-auto', managedPermission: permission, ...(fast?.allowed === true ? {managedFast: fast.enabled === true ? 'on' as const : 'off' as const} : {}), managedHelpers:{active:Number.isInteger(counter?.active) && counter.active>=0 && counter.active<=maximum ? counter.active : 0, maximum},
30
32
  ...(Array.isArray(capabilities)?{managedThinkingLevels:levels.filter(level=>capabilities.includes(level))}:{}),
31
33
  ...(steps ? {managedPlan:{steps}} : {}), ...(process ? {managedProcess:process} : {}), ...(turnEnd ? {managedTurnEnd:turnEnd} : {})};
32
34
  }
@@ -32,6 +32,15 @@ export async function executePermissionCommand(session: any,
32
32
  if (!profile || profile.mode !== permissionMode || profile.warning) throw new Error("session-permission-unavailable");
33
33
  }
34
34
 
35
+ // Fast mode of a company conversation, where its Harness allows it. Like the
36
+ // permission, the runtime reads it at each request, so it may change mid-turn.
37
+ // True when the conversation's setting changed.
38
+ export function executeFastCommand(session: any, fast: boolean): boolean {
39
+ if (!session) throw new Error("session-runtime-unavailable");
40
+ if (!session.managedExecution || typeof session.managedSetFast !== "function") throw new Error("managed-fast-unavailable");
41
+ return session.managedSetFast(fast) === true;
42
+ }
43
+
35
44
  export async function executeRuntimeCommand(session: any, command: string): Promise<{
36
45
  outputs: RuntimeCommandOutput[];
37
46
  modelCallObserved: boolean;
@@ -49,7 +49,7 @@ function catalogRef(key: Buffer, namespace: string, value: string): string {
49
49
  }
50
50
 
51
51
  export type SessionOwnerProjection = Pick<SessionRow, "state" | "liveState" | "composerAvailable" | "needsAttention" | "owner" | "reasonCode">;
52
- export type SessionOptionProjection = Pick<SessionRow, "modelLabel" | "thinkingLevel" | "managedHelpers" | "managedThinkingLevels" | "managedPermission" | "managedPlan" | "managedProcess" | "managedTurnEnd">;
52
+ export type SessionOptionProjection = Pick<SessionRow, "modelLabel" | "thinkingLevel" | "managedHelpers" | "managedThinkingLevels" | "managedPermission" | "managedFast" | "managedPlan" | "managedProcess" | "managedTurnEnd">;
53
53
 
54
54
  function display(value: unknown, maximum: number, fallback: string): string {
55
55
  const clean = redactSensitiveText(String(value ?? "")).text
@@ -135,6 +135,7 @@ function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undef
135
135
  active: ownership?.liveState === 'idle' ? 0 : ownership?.liveState === 'running' || ownership?.liveState === 'waiting-approval' ? sessionOptions.managedHelpers.active : null}} : {}),
136
136
  ...(sessionOptions?.managedThinkingLevels ? {managedThinkingLevels:sessionOptions.managedThinkingLevels} : {}),
137
137
  ...(sessionOptions?.managedPermission ? {managedPermission:sessionOptions.managedPermission} : {}),
138
+ ...(sessionOptions?.managedFast ? {managedFast:sessionOptions.managedFast} : {}),
138
139
  ...(sessionOptions?.managedPlan ? {managedPlan:sessionOptions.managedPlan} : {}),
139
140
  ...(sessionOptions?.managedProcess ? {managedProcess:sessionOptions.managedProcess} : {}),
140
141
  ...(sessionOptions?.managedTurnEnd ? {managedTurnEnd:sessionOptions.managedTurnEnd} : {}),
@@ -146,7 +147,7 @@ function row(key: Buffer, info: PiSessionInfo, metadata: SessionMetadata | undef
146
147
  ? { kind: "none", ownerEpoch: null, gatewayInstanceRef: null, runtimeInstanceRef: null, continuity: "released" }
147
148
  : ownership?.owner ?? { kind: "none", ownerEpoch: null, gatewayInstanceRef: null, runtimeInstanceRef: null, continuity: "unknown" },
148
149
  sessionRevision: revision(key, [info.path, info.modified.toISOString(), info.messageCount, info.name ?? null, metadata?.revision ?? null,
149
- sessionOptions?.modelLabel ?? null, sessionOptions?.thinkingLevel ?? "unknown", sessionOptions?.managedHelpers, sessionOptions?.managedThinkingLevels, sessionOptions?.managedPermission ?? null, sessionOptions?.managedPlan, sessionOptions?.managedProcess, sessionOptions?.managedTurnEnd,
150
+ sessionOptions?.modelLabel ?? null, sessionOptions?.thinkingLevel ?? "unknown", sessionOptions?.managedHelpers, sessionOptions?.managedThinkingLevels, sessionOptions?.managedPermission ?? null, sessionOptions?.managedFast ?? null, sessionOptions?.managedPlan, sessionOptions?.managedProcess, sessionOptions?.managedTurnEnd,
150
151
  gitBranch, gitRepositories,
151
152
  metadata?.archived ? "archived" : ownership ? [ownership.state, ownership.liveState, ownership.owner.kind,
152
153
  ownership.owner.ownerEpoch, ownership.owner.runtimeInstanceRef, ownership.reasonCode] : "offline"]),
@@ -13,7 +13,7 @@ import { SessionMetadataStore } from "./session-metadata-store.ts";
13
13
  const REF = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,159}$/;
14
14
  const REVISION = /^[A-Za-z0-9][A-Za-z0-9._:~-]{0,159}$/;
15
15
  const IDEMPOTENCY = /^[A-Za-z0-9_-]{22,96}$/;
16
- const ACTIONS: SessionAction[] = ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.rename", "session.pin", "session.archive",
16
+ const ACTIONS: SessionAction[] = ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.set-fast", "session.rename", "session.pin", "session.archive",
17
17
  "session.unarchive", "session.fork", "session.acquire", "session.release"];
18
18
 
19
19
  type SessionCommand = SessionCommandIdentity & {
@@ -58,6 +58,7 @@ function validPayload(action: SessionAction, payload: Record<string, unknown>):
58
58
  && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(String(payload.thinkingLevel));
59
59
  if (action === "session.set-permission") return exactKeys(payload, ["permissionMode"])
60
60
  && ["read-only", "workspace-write", "trusted-full-access"].includes(String(payload.permissionMode));
61
+ if (action === "session.set-fast") return exactKeys(payload, ["fast"]) && typeof payload.fast === "boolean";
61
62
  if (action === "session.rename") return exactKeys(payload, ["title"]) && typeof payload.title === "string"
62
63
  && payload.title.length >= 1 && payload.title.length <= 500 && !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(payload.title);
63
64
  if (action === "session.pin") return exactKeys(payload, ["pinned"]) && typeof payload.pinned === "boolean";
@@ -160,7 +161,7 @@ export class SessionCommandController {
160
161
  let deferredDispatch: Pick<SessionSendResult, "dispatch" | "cancel"> | null = null;
161
162
  let observedCatalog = current, observedRow = row;
162
163
  try {
163
- let resultCode: "created" | "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "permission-changed"
164
+ let resultCode: "created" | "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "fast-changed" | "permission-changed"
164
165
  | "renamed" | "pinned" | "unpinned" | "archived" | "unarchived" | "forked" | "no-change",
165
166
  operationRef: string | null = null;
166
167
  if (command.action === "session.create") {
@@ -228,6 +229,8 @@ export class SessionCommandController {
228
229
  } else if (command.action === "session.set-permission") {
229
230
  resultCode = await this.#runtimes.setPermission(command.sessionRef!, String(command.payload.permissionMode) as
230
231
  "read-only" | "workspace-write" | "trusted-full-access");
232
+ } else if (command.action === "session.set-fast") {
233
+ resultCode = await this.#runtimes.setFast(command.sessionRef!, command.payload.fast === true);
231
234
  } else if (command.action === "session.rename") {
232
235
  resultCode = await this.#runtimes.rename(command.sessionRef!, String(command.payload.title).trim());
233
236
  } else if (command.action === "session.fork") {
@@ -320,7 +323,7 @@ export class SessionCommandController {
320
323
  sessionRevisionAfter: row?.sessionRevision ?? null, deduplicated: false };
321
324
  }
322
325
  #settled(command: SessionCommand, catalog: Catalog, row: SessionRow,
323
- resultCode: "created" | "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "permission-changed"
326
+ resultCode: "created" | "acquired" | "released" | "started" | "queued" | "steered" | "aborted" | "model-changed" | "thinking-changed" | "fast-changed" | "permission-changed"
324
327
  | "renamed" | "pinned" | "unpinned" | "archived" | "unarchived" | "forked" | "no-change",
325
328
  operationRef: string | null): Receipt {
326
329
  return { ...this.#base(command, catalog, row), operationRef, phase: "settled", resultCode, settledAt: this.#now().toISOString(),
@@ -11,7 +11,7 @@ const REF = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,159}$/;
11
11
  const REVISION = /^[A-Za-z0-9][A-Za-z0-9._:~-]{0,159}$/;
12
12
  const REASON = /^[a-z0-9][a-z0-9.-]{0,95}$/;
13
13
  const DIGEST = /^sha256:[a-f0-9]{64}$/;
14
- const ACTIONS = ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.rename", "session.pin", "session.archive",
14
+ const ACTIONS = ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.set-fast", "session.rename", "session.pin", "session.archive",
15
15
  "session.unarchive", "session.fork", "session.acquire", "session.release"] as const;
16
16
 
17
17
  export type SessionAction = typeof ACTIONS[number];
@@ -72,14 +72,14 @@ export function validSessionReceipt(value: unknown): value is Receipt {
72
72
  const settled = exactTimestamp(receipt.settledAt), evidence = typeof receipt.evidenceRef === "string" && REF.test(receipt.evidenceRef);
73
73
  if (receipt.phase === "accepted") return receipt.resultCode === "accepted" && receipt.settledAt === null && receipt.evidenceRef === null && receipt.error === null;
74
74
  if (receipt.phase === "settled") {
75
- const results = ["created", "started", "queued", "steered", "aborted", "model-changed", "thinking-changed", "permission-changed", "renamed", "pinned", "unpinned", "archived", "unarchived",
75
+ const results = ["created", "started", "queued", "steered", "aborted", "model-changed", "thinking-changed", "permission-changed", "fast-changed", "renamed", "pinned", "unpinned", "archived", "unarchived",
76
76
  "forked", "acquired", "released", "no-change"];
77
77
  const actionResults: Partial<Record<SessionAction, string[]>> = {
78
78
  "session.create": ["created", "started"],
79
79
  "session.acquire": ["acquired", "no-change"], "session.release": ["released", "no-change"],
80
80
  "session.send": ["started", "queued", "steered"], "session.abort": ["aborted", "no-change"],
81
81
  "session.set-model": ["model-changed", "no-change"], "session.set-thinking": ["thinking-changed", "no-change"],
82
- "session.set-permission": ["permission-changed", "no-change"], "session.rename": ["renamed", "no-change"],
82
+ "session.set-permission": ["permission-changed", "no-change"], "session.set-fast": ["fast-changed", "no-change"], "session.rename": ["renamed", "no-change"],
83
83
  "session.pin": ["pinned", "unpinned", "no-change"], "session.archive": ["archived", "no-change"],
84
84
  "session.unarchive": ["unarchived", "no-change"], "session.fork": ["forked"]
85
85
  };
@@ -34,7 +34,12 @@ export function inspectEffectiveSessionOptions(session: any, modelRef: string |
34
34
  thinkingLevel: null, reasonCode: "session-thinking-effect-unknown" };
35
35
  if (modelRef && effectiveModel !== modelRef) return { state: "mismatch", modelRef: effectiveModel,
36
36
  thinkingLevel: effectiveThinking, reasonCode: "session-model-mismatch" };
37
- if (effectiveThinking !== thinkingLevel) return { state: "mismatch", modelRef: effectiveModel,
37
+ // A company conversation thinks at a level its Harness allows. The new chat
38
+ // page can still offer the levels of the Harness before an administrator
39
+ // changed it (high, when the Harness now fixes max): the runtime takes the
40
+ // nearest allowed level, and that is the conversation's level, not a create
41
+ // that failed. Refusing it left the message unsent under a "resyncing" notice.
42
+ if (effectiveThinking !== thinkingLevel && session?.managedExecution !== true) return { state: "mismatch", modelRef: effectiveModel,
38
43
  thinkingLevel: effectiveThinking, reasonCode: "session-thinking-mismatch" };
39
44
  return { state: "confirmed", modelRef: effectiveModel, thinkingLevel: effectiveThinking, reasonCode: null };
40
45
  }
@@ -11,7 +11,7 @@ import { isUserConversationSession, projectRefForCwd, sessionRefForPath, type Se
11
11
  import { SessionLeaseStore, type SessionLeaseSnapshot } from "./session-lease-store.ts";
12
12
  import { GatewayEventStore } from "./gateway-events.ts";
13
13
  import { GatewaySessionStream } from "./gateway-session-stream.ts";
14
- import { executePermissionCommand, executeRuntimeCommand } from "./runtime-session-controls.ts";
14
+ import { executeFastCommand, executePermissionCommand, executeRuntimeCommand } from "./runtime-session-controls.ts";
15
15
  import { inspectEffectiveSessionOptions, effectiveModelRef, effectiveThinkingLevel, type EffectiveSessionOptions } from "./session-effective-options.ts";
16
16
  import { launchSessionPrompt, type LaunchedSessionPrompt, type SessionSendResult } from "./session-prompt-dispatch.ts";
17
17
  import { WEBUI_MESSAGE_CORRELATION_ENTRY_TYPE } from "../shared/message-correlation.ts";
@@ -348,22 +348,19 @@ export class SessionRuntimeSupervisor {
348
348
  const effective = effectiveModelRef(session);
349
349
  if (!effective) throw new Error("session-model-effect-unknown");
350
350
  if (effective !== modelRef) throw new Error("session-model-mismatch");
351
- this.#touchCreated(sessionRef);
352
- return "model-changed";
351
+ this.#touchCreated(sessionRef); return "model-changed";
353
352
  }
354
353
  async setThinking(sessionRef: string, thinkingLevel: string): Promise<"thinking-changed" | "no-change"> {
355
354
  await this.acquire(sessionRef);
356
355
  const active = this.#active.get(sessionRef), session = active?.runtime.session;
357
356
  if (!active || !session) throw new Error("session-runtime-unavailable");
358
357
  if (active.operationRef || !session.isIdle) throw new Error("session-runtime-busy");
359
- const current = effectiveThinkingLevel(session);
360
- if (current === thinkingLevel) return "no-change";
358
+ if (effectiveThinkingLevel(session) === thinkingLevel) return "no-change";
361
359
  await session.setThinkingLevel(thinkingLevel);
362
360
  const effective = effectiveThinkingLevel(session);
363
361
  if (!effective) throw new Error("session-thinking-effect-unknown");
364
362
  if (effective !== thinkingLevel) throw new Error("session-thinking-mismatch");
365
- this.#touchCreated(sessionRef);
366
- return "thinking-changed";
363
+ this.#touchCreated(sessionRef); return "thinking-changed";
367
364
  }
368
365
  async setPermission(sessionRef: string, permissionMode: "read-only" | "workspace-write" | "trusted-full-access"): Promise<"permission-changed"> {
369
366
  await this.acquire(sessionRef);
@@ -371,8 +368,11 @@ export class SessionRuntimeSupervisor {
371
368
  if (!active || !session) throw new Error("session-runtime-unavailable");
372
369
  if (!session.managedExecution && (active.operationRef || !session.isIdle)) throw new Error("session-runtime-busy");
373
370
  await executePermissionCommand(session, permissionMode);
374
- this.#touchCreated(sessionRef);
375
- return "permission-changed";
371
+ this.#touchCreated(sessionRef); return "permission-changed";
372
+ }
373
+ async setFast(sessionRef: string, fast: boolean): Promise<"fast-changed" | "no-change"> {
374
+ await this.acquire(sessionRef); const changed = executeFastCommand(this.#active.get(sessionRef)?.runtime.session, fast);
375
+ this.#touchCreated(sessionRef); return changed ? "fast-changed" : "no-change";
376
376
  }
377
377
  async runRuntimeCommand(sessionRef: string, command: string): Promise<RuntimeCommandResult> {
378
378
  await this.acquire(sessionRef);
@@ -92,7 +92,7 @@
92
92
  "composerAvailable": { "type": "boolean" },
93
93
  "needsAttention": { "type": "boolean" },
94
94
  "managedHelpers": { "type": "object", "additionalProperties": false, "required": ["active", "maximum"], "properties": {"active": {"type": ["integer", "null"], "minimum": 0, "maximum": 4}, "maximum": {"type": "integer", "minimum": 0, "maximum": 4}} },
95
- "managedThinkingLevels": {"type": "array", "uniqueItems": true, "maxItems": 7, "items": {"enum": ["off","minimal","low","medium","high","xhigh","max"]}}, "managedPermission": {"enum": ["workspace-write","trusted-full-access"]},
95
+ "managedThinkingLevels": {"type": "array", "uniqueItems": true, "maxItems": 7, "items": {"enum": ["off","minimal","low","medium","high","xhigh","max"]}}, "managedPermission": {"enum": ["workspace-write","trusted-full-access"]}, "managedFast": {"enum": ["off","on"]},
96
96
  "managedPlan": { "type": "object", "additionalProperties": false, "required": ["steps"], "properties": {
97
97
  "steps": { "type": "array", "minItems": 1, "maxItems": 30, "items": { "type": "object", "additionalProperties": false, "required": ["step", "status"], "properties": {
98
98
  "step": { "type": "string", "minLength": 1, "maxLength": 200 }, "status": { "enum": ["pending", "in_progress", "completed", "deferred"] } } } } } },
@@ -29,7 +29,7 @@
29
29
  "messageType": { "const": "command" },
30
30
  "commandId": { "$ref": "common-v1.schema.json#/$defs/opaqueRef" },
31
31
  "idempotencyKey": { "$ref": "#/$defs/idempotencyKey" },
32
- "action": { "enum": ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.rename", "session.pin", "session.archive", "session.unarchive", "session.fork", "session.acquire", "session.release"] },
32
+ "action": { "enum": ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.set-fast", "session.rename", "session.pin", "session.archive", "session.unarchive", "session.fork", "session.acquire", "session.release"] },
33
33
  "requestedAt": { "$ref": "common-v1.schema.json#/$defs/timestamp" },
34
34
  "expiresAt": { "$ref": "common-v1.schema.json#/$defs/timestamp" },
35
35
  "sessionRef": { "$ref": "common-v1.schema.json#/$defs/nullableOpaqueRef" },
@@ -144,7 +144,7 @@
144
144
  {
145
145
  "type": "object",
146
146
  "properties": {
147
- "action": { "enum": ["session.set-model", "session.set-thinking", "session.set-permission"] },
147
+ "action": { "enum": ["session.set-model", "session.set-thinking", "session.set-permission", "session.set-fast"] },
148
148
  "sessionRef": { "$ref": "common-v1.schema.json#/$defs/opaqueRef" },
149
149
  "expectedSessionRevision": { "$ref": "common-v1.schema.json#/$defs/revisionToken" },
150
150
  "payload": {
@@ -166,6 +166,12 @@
166
166
  "additionalProperties": false,
167
167
  "required": ["permissionMode"],
168
168
  "properties": { "permissionMode": { "$ref": "#/$defs/permissionMode" } }
169
+ },
170
+ {
171
+ "type": "object",
172
+ "additionalProperties": false,
173
+ "required": ["fast"],
174
+ "properties": { "fast": { "type": "boolean" } }
169
175
  }
170
176
  ]
171
177
  }
@@ -185,6 +191,11 @@
185
191
  "if": { "properties": { "action": { "const": "session.set-permission" } }, "required": ["action"] },
186
192
  "then": { "properties": { "payload": { "type": "object", "required": ["permissionMode"],
187
193
  "properties": { "permissionMode": { "$ref": "#/$defs/permissionMode" } } } } }
194
+ },
195
+ {
196
+ "if": { "properties": { "action": { "const": "session.set-fast" } }, "required": ["action"] },
197
+ "then": { "properties": { "payload": { "type": "object", "required": ["fast"],
198
+ "properties": { "fast": { "type": "boolean" } } } } }
188
199
  }
189
200
  ]
190
201
  }
@@ -290,9 +301,9 @@
290
301
  "messageType": { "const": "receipt" },
291
302
  "commandId": { "$ref": "common-v1.schema.json#/$defs/opaqueRef" },
292
303
  "idempotencyKeyDigest": { "$ref": "common-v1.schema.json#/$defs/digest" },
293
- "action": { "enum": ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.rename", "session.pin", "session.archive", "session.unarchive", "session.fork", "session.acquire", "session.release"] },
304
+ "action": { "enum": ["session.create", "session.send", "session.abort", "session.set-model", "session.set-thinking", "session.set-permission", "session.set-fast", "session.rename", "session.pin", "session.archive", "session.unarchive", "session.fork", "session.acquire", "session.release"] },
294
305
  "phase": { "enum": ["accepted", "settled", "uncertain", "rejected"] },
295
- "resultCode": { "enum": ["accepted", "created", "started", "queued", "steered", "aborted", "model-changed", "thinking-changed", "permission-changed", "renamed", "pinned", "unpinned", "archived", "unarchived", "forked", "acquired", "released", "no-change", "stale-revision", "owner-conflict", "recovery-required", "invalid-command", "expired", "unavailable", "effect-unknown"] },
306
+ "resultCode": { "enum": ["accepted", "created", "started", "queued", "steered", "aborted", "model-changed", "thinking-changed", "permission-changed", "fast-changed", "renamed", "pinned", "unpinned", "archived", "unarchived", "forked", "acquired", "released", "no-change", "stale-revision", "owner-conflict", "recovery-required", "invalid-command", "expired", "unavailable", "effect-unknown"] },
296
307
  "requestedAt": { "$ref": "common-v1.schema.json#/$defs/timestamp" },
297
308
  "settledAt": { "anyOf": [{ "$ref": "common-v1.schema.json#/$defs/timestamp" }, { "type": "null" }] },
298
309
  "sessionRef": { "$ref": "common-v1.schema.json#/$defs/nullableOpaqueRef" },
@@ -325,7 +336,7 @@
325
336
  "if": { "properties": { "phase": { "const": "settled" } }, "required": ["phase"] },
326
337
  "then": {
327
338
  "properties": {
328
- "resultCode": { "enum": ["created", "started", "queued", "steered", "aborted", "model-changed", "thinking-changed", "permission-changed", "renamed", "pinned", "unpinned", "archived", "unarchived", "forked", "acquired", "released", "no-change"] },
339
+ "resultCode": { "enum": ["created", "started", "queued", "steered", "aborted", "model-changed", "thinking-changed", "permission-changed", "fast-changed", "renamed", "pinned", "unpinned", "archived", "unarchived", "forked", "acquired", "released", "no-change"] },
329
340
  "settledAt": { "$ref": "common-v1.schema.json#/$defs/timestamp" },
330
341
  "evidenceRef": { "$ref": "common-v1.schema.json#/$defs/opaqueRef" },
331
342
  "error": { "type": "null" }
@@ -367,6 +378,10 @@
367
378
  "if": { "properties": { "action": { "const": "session.set-permission" }, "phase": { "const": "settled" } }, "required": ["action", "phase"] },
368
379
  "then": { "properties": { "resultCode": { "enum": ["permission-changed", "no-change"] } } }
369
380
  },
381
+ {
382
+ "if": { "properties": { "action": { "const": "session.set-fast" }, "phase": { "const": "settled" } }, "required": ["action", "phase"] },
383
+ "then": { "properties": { "resultCode": { "enum": ["fast-changed", "no-change"] } } }
384
+ },
370
385
  {
371
386
  "if": { "properties": { "action": { "const": "session.send" }, "phase": { "const": "settled" } }, "required": ["action", "phase"] },
372
387
  "then": { "properties": { "resultCode": { "enum": ["started", "queued", "steered"] }, "operationRef": { "$ref": "common-v1.schema.json#/$defs/opaqueRef" } } }
@@ -83,6 +83,28 @@ export function companyTerminalExtension({ managed, packageRoot, sdkRoot, bindin
83
83
  ctx.ui.notify(PERMISSION_TEXT[session.permission] ?? PERMISSION_TEXT['workspace-write'], 'info');
84
84
  } });
85
85
 
86
+ // Fast mode, where the team's Harness allows it; off until the member turns it on.
87
+ const showFast = (ctx) => ctx.ui?.setStatus?.('agent-watch-fast', managed()?.session?.managedFastState?.() === 'on' ? 'Fast' : undefined);
88
+ pi.on('session_start', async (_event, ctx) => showFast(ctx));
89
+ pi.registerCommand('fast', {
90
+ description: 'Bật/tắt Fast cho cuộc trò chuyện công ty này (on, off, status); chỉ có khi Harness của team cho phép',
91
+ getArgumentCompletions: (prefix) => ['on', 'off', 'status'].filter((v) => v.startsWith(prefix)).map((value) => ({ value, label: value })),
92
+ handler: async (args, ctx) => {
93
+ const session = managed()?.session, state = session?.managedFastState?.();
94
+ if (typeof session?.managedSetFast !== 'function') { ctx.ui.notify('Cuộc trò chuyện này chưa sẵn sàng.', 'error'); return; }
95
+ if (!state) { ctx.ui.notify('Harness của team không cho phép Fast. Admin bật trong Agent Studio → Harness → Fast mode.', 'warning'); return; }
96
+ const arg = args.trim().toLowerCase();
97
+ if (!['', 'on', 'off', 'status'].includes(arg)) { ctx.ui.notify('Cách dùng: /fast on|off|status', 'warning'); return; }
98
+ if (arg === 'on' || arg === 'off') session.managedSetFast(arg === 'on');
99
+ else if (arg === '') session.managedSetFast(state !== 'on');
100
+ showFast(ctx);
101
+ ctx.ui.notify(session.managedFastState() === 'on'
102
+ ? session.managedFastServed?.() === false
103
+ ? 'Fast đang bật nhưng model hiện tại của main agent không hỗ trợ Fast (chỉ các model GPT qua Codex): các lượt vẫn chạy tốc độ thường, quota tính như thường.'
104
+ : 'Fast đang bật: main agent trả lời nhanh hơn trên model GPT, token tính vào quota gấp 2,5 lần. Model và mức suy luận không đổi.'
105
+ : 'Fast đang tắt: các lượt chạy tốc độ thường.', 'info');
106
+ } });
107
+
86
108
  // The Harness's notes carry English for the agent; the member reads them in Vietnamese.
87
109
  pi.registerMessageRenderer('agent-watch-process', (message, _options, theme) => {
88
110
  const lines = processLines(message.details);