@zq-silk/yui 1.0.0 → 1.0.1
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/dist/commands/taskCommands.js +2 -3
- package/dist/executor/fileRoleLaunchPlanner.js +12 -3
- package/dist/runtime/agentHost.js +5 -0
- package/dist/runtime/sessionTitle.js +15 -0
- package/dist/runtime/structuredProviderHost.js +46 -6
- package/docs/managed-turn-and-session-runtime.md +7 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +5 -0
- package/package.json +1 -1
- package/skills/yui-operator/SKILL.md +35 -15
|
@@ -827,9 +827,8 @@ function createTaskCommand(args, store, options) {
|
|
|
827
827
|
return output(`Created Draft task ${created.task.id}: ${created.task.title}\n`
|
|
828
828
|
+ `Assigned role: ${created.leader.name}\n`
|
|
829
829
|
+ `Type: ${created.task.type ?? "unspecified"}\n`
|
|
830
|
-
+
|
|
831
|
-
|
|
832
|
-
: "Execution: no Project delivery evidence required\n"), {
|
|
830
|
+
+ "Status: Draft; discussion and development have not started\n"
|
|
831
|
+
+ "Next: explicitly submit with intent discuss or develop when the Task should start\n", {
|
|
833
832
|
task: created.task,
|
|
834
833
|
leader: created.leader
|
|
835
834
|
});
|
|
@@ -17,7 +17,7 @@ import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
|
|
|
17
17
|
import { builtinAgentEndpointImplementation, validateAgentEndpointImplementation } from "../runtime/agentEndpointIdentity.js";
|
|
18
18
|
import { builtinAgentDriverRegistry, builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
19
19
|
import { assertProviderConversationReplaceable } from "../runtime/providerRuntimeIdentity.js";
|
|
20
|
-
import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
20
|
+
import { operatorSessionTitle, resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
21
21
|
import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment } from "../runtime/taskRuntimeIsolation.js";
|
|
22
22
|
import { planningRuntimeCwd } from "../storage/homeLayout.js";
|
|
23
23
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
@@ -33,6 +33,7 @@ export class FileRoleLaunchPlanner {
|
|
|
33
33
|
#agentEnvironment;
|
|
34
34
|
#nativeAgentEnvironment;
|
|
35
35
|
#createNativeSessionId;
|
|
36
|
+
#now;
|
|
36
37
|
#cliPath;
|
|
37
38
|
#inspectWorkspacePhysicalState;
|
|
38
39
|
#entryPoint;
|
|
@@ -51,6 +52,7 @@ export class FileRoleLaunchPlanner {
|
|
|
51
52
|
this.#agentEnvironment = this.#selectConfiguredAgentEnvironment(sourceEnvironment);
|
|
52
53
|
this.#nativeAgentEnvironment = this.#selectConfiguredNativeEnvironment(sourceEnvironment);
|
|
53
54
|
this.#createNativeSessionId = options.createNativeSessionId ?? randomUUID;
|
|
55
|
+
this.#now = options.now ?? (() => new Date());
|
|
54
56
|
this.#inspectWorkspacePhysicalState = options.inspectWorkspacePhysicalState
|
|
55
57
|
?? inspectWorkspacePhysicalState;
|
|
56
58
|
this.#cliPath = canonicalPath(options.cliPath
|
|
@@ -259,7 +261,9 @@ export class FileRoleLaunchPlanner {
|
|
|
259
261
|
if (input.mode === "new" && sessionSet?.providerBinding != null) {
|
|
260
262
|
assertProviderConversationReplaceable(sessionSet.providerBinding);
|
|
261
263
|
}
|
|
262
|
-
return this.#compile(role, input, { scope: "global" },
|
|
264
|
+
return this.#compile(role, input, { scope: "global" }, input.mode === "new" && role.name === "operator"
|
|
265
|
+
? operatorSessionTitle(this.#now(), this.store.getConfig().timeZone)
|
|
266
|
+
: undefined, input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, undefined, effective, { purpose: "execution" });
|
|
263
267
|
}
|
|
264
268
|
#compile(role, input, owner, sessionTitle, knownNativeSessionId, workspaceOverride, effective, sessionPolicy) {
|
|
265
269
|
const launchRole = effectiveRoleForLaunch(role, effective);
|
|
@@ -389,11 +393,16 @@ export class FileRoleLaunchPlanner {
|
|
|
389
393
|
const effectiveConfig = withNativeProjectDirectories(roleConfig, effective.executionEnvironment === undefined
|
|
390
394
|
? nativeAdditionalDirectories(effective.workspace, agentWorkspace)
|
|
391
395
|
: []);
|
|
396
|
+
const postCreateOperatorTitle = owner.scope === "global"
|
|
397
|
+
&& role.name === "operator"
|
|
398
|
+
&& sessionTitle !== undefined;
|
|
392
399
|
const compileInput = {
|
|
393
400
|
agent,
|
|
394
401
|
config: effectiveConfig,
|
|
395
402
|
workspace: agentWorkspace,
|
|
396
|
-
...(sessionTitle === undefined
|
|
403
|
+
...(sessionTitle === undefined || postCreateOperatorTitle
|
|
404
|
+
? {}
|
|
405
|
+
: { sessionTitle }),
|
|
397
406
|
...sessionContext
|
|
398
407
|
};
|
|
399
408
|
if (input.mode === "resume"
|
|
@@ -328,7 +328,12 @@ export async function runAgentHost(input) {
|
|
|
328
328
|
kind: "restore",
|
|
329
329
|
mode: "resume",
|
|
330
330
|
nativeSessionId: disconnectedSession.nativeSessionId,
|
|
331
|
+
// The dated Operator title is a one-shot effect of creating the
|
|
332
|
+
// Conversation. Reattaching this client must not replay metadata whose
|
|
333
|
+
// earlier outcome may be unknown.
|
|
331
334
|
...(previousControl.sessionTitle === undefined
|
|
335
|
+
|| (currentPayload.environment.YUI_SESSION_SCOPE === "global"
|
|
336
|
+
&& currentPayload.environment.YUI_ROLE === "operator")
|
|
332
337
|
? {}
|
|
333
338
|
: { sessionTitle: previousControl.sessionTitle }),
|
|
334
339
|
codexThread: disconnectedSession.configuration.threadOptions,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveTimeZone } from "../config/timeZone.js";
|
|
1
2
|
const TITLE_SEPARATOR = " · ";
|
|
2
3
|
export const MAX_SESSION_TITLE_LENGTH = 80;
|
|
3
4
|
const TASK_TITLE_MAX_LENGTH = 20;
|
|
@@ -22,6 +23,20 @@ export function resolveTaskRoleSessionTitle(existingTitle, task, roleName) {
|
|
|
22
23
|
}
|
|
23
24
|
return taskRoleSessionTitle(task, roleName);
|
|
24
25
|
}
|
|
26
|
+
export function operatorSessionTitle(now, configuredTimeZone) {
|
|
27
|
+
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) {
|
|
28
|
+
throw new TypeError("Operator session title date is invalid.");
|
|
29
|
+
}
|
|
30
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-CA", {
|
|
31
|
+
timeZone: resolveTimeZone(configuredTimeZone),
|
|
32
|
+
month: "2-digit",
|
|
33
|
+
day: "2-digit"
|
|
34
|
+
}).formatToParts(now).map(({ type, value }) => [type, value]));
|
|
35
|
+
if (parts.month === undefined || parts.day === undefined) {
|
|
36
|
+
throw new TypeError("Operator session title date is unavailable.");
|
|
37
|
+
}
|
|
38
|
+
return `Yui${TITLE_SEPARATOR}Operator${TITLE_SEPARATOR}${parts.month}${parts.day}`;
|
|
39
|
+
}
|
|
25
40
|
function displayTitle(value, maxLength) {
|
|
26
41
|
if (value.length <= maxLength)
|
|
27
42
|
return value;
|
|
@@ -68,10 +68,21 @@ export async function startStructuredProviderSession(payload, input = {}) {
|
|
|
68
68
|
...(input.onDiagnostic === undefined ? {} : { onDiagnostic: input.onDiagnostic }),
|
|
69
69
|
mirror
|
|
70
70
|
});
|
|
71
|
+
if (isPostCreateOperatorTitle(payload)) {
|
|
72
|
+
input.onDiagnostic?.({
|
|
73
|
+
nativeSessionId: session.nativeSessionId,
|
|
74
|
+
failure: {
|
|
75
|
+
detail: "This Agent protocol does not support setting a native Session title.",
|
|
76
|
+
phase: "session-start",
|
|
77
|
+
inputDisposition: "not-accepted",
|
|
78
|
+
sessionDisposition: "recoverable"
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
71
82
|
return Object.freeze({ session });
|
|
72
83
|
}
|
|
73
84
|
if (control.adapterId === "codex") {
|
|
74
|
-
const opened = await CodexStructuredProviderSession.open(child, exit, processInstanceId, payload, control, input.onStarted, input.onTerminal, input.onGoal, input.onInput, input.onActivity, mirror);
|
|
85
|
+
const opened = await CodexStructuredProviderSession.open(child, exit, processInstanceId, payload, control, input.onStarted, input.onTerminal, input.onGoal, input.onInput, input.onActivity, input.onDiagnostic, mirror);
|
|
75
86
|
return Object.freeze({
|
|
76
87
|
session: opened.session,
|
|
77
88
|
...(opened.recoveredTerminal === undefined
|
|
@@ -81,6 +92,17 @@ export async function startStructuredProviderSession(payload, input = {}) {
|
|
|
81
92
|
});
|
|
82
93
|
}
|
|
83
94
|
const session = await ClaudeStructuredProviderSession.open(child, exit, processInstanceId, control, input.onAccepted, input.onTerminal, input.onGoal, input.onActivity, input.onDiagnostic, mirror);
|
|
95
|
+
if (isPostCreateOperatorTitle(payload)) {
|
|
96
|
+
input.onDiagnostic?.({
|
|
97
|
+
nativeSessionId: session.nativeSessionId,
|
|
98
|
+
failure: {
|
|
99
|
+
detail: "This Agent provider does not support renaming a Session after creation.",
|
|
100
|
+
phase: "session-start",
|
|
101
|
+
inputDisposition: "not-accepted",
|
|
102
|
+
sessionDisposition: "recoverable"
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
84
106
|
return Object.freeze({ session });
|
|
85
107
|
}
|
|
86
108
|
catch (error) {
|
|
@@ -88,6 +110,12 @@ export async function startStructuredProviderSession(payload, input = {}) {
|
|
|
88
110
|
throw error;
|
|
89
111
|
}
|
|
90
112
|
}
|
|
113
|
+
function isPostCreateOperatorTitle(payload) {
|
|
114
|
+
return payload.providerControl?.sessionTitle !== undefined
|
|
115
|
+
&& payload.providerControl.mode === "new"
|
|
116
|
+
&& payload.environment.YUI_SESSION_SCOPE === "global"
|
|
117
|
+
&& payload.environment.YUI_ROLE === "operator";
|
|
118
|
+
}
|
|
91
119
|
/**
|
|
92
120
|
* Open an uninitialized, transparent connection for a native TUI. The TUI
|
|
93
121
|
* owns its requests; Yui observes only its exact startup response.
|
|
@@ -351,7 +379,7 @@ class CodexStructuredProviderSession {
|
|
|
351
379
|
this.mirror = mirror;
|
|
352
380
|
this.nativeAccountHome = nativeAccountHome;
|
|
353
381
|
}
|
|
354
|
-
static async open(child, exit, processInstanceId, payload, control, onStarted, onTerminal, onGoal, onInput, onActivity, mirror) {
|
|
382
|
+
static async open(child, exit, processInstanceId, payload, control, onStarted, onTerminal, onGoal, onInput, onActivity, onDiagnostic, mirror) {
|
|
355
383
|
const channel = await CodexProxyWebSocketChannel.connect(child, mirror);
|
|
356
384
|
const openingMessages = [];
|
|
357
385
|
const stopOpeningBuffer = channel.onMessage((message) => openingMessages.push(message));
|
|
@@ -385,10 +413,22 @@ class CodexStructuredProviderSession {
|
|
|
385
413
|
}
|
|
386
414
|
}
|
|
387
415
|
if (control.sessionTitle !== undefined) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
416
|
+
try {
|
|
417
|
+
await runtime.setConversationName({
|
|
418
|
+
conversationId,
|
|
419
|
+
name: control.sessionTitle
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
onDiagnostic?.({
|
|
424
|
+
nativeSessionId: conversationId,
|
|
425
|
+
failure: providerDeliveryFailureFrom(error, {
|
|
426
|
+
phase: "session-start",
|
|
427
|
+
inputDisposition: "not-accepted",
|
|
428
|
+
sessionDisposition: "recoverable"
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
}
|
|
392
432
|
}
|
|
393
433
|
const session = new CodexStructuredProviderSession(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, mirror, optionalId(initialized.codexHome));
|
|
394
434
|
session.#activeTurnId = resumedActiveTurnId;
|
|
@@ -11,6 +11,13 @@ notifications cannot restart an explicitly stopped Session; normal Host detach
|
|
|
11
11
|
preserves an active Session and remains reconnectable. A switch after a partial
|
|
12
12
|
failure still settles the retained input before selecting a new conversation.
|
|
13
13
|
|
|
14
|
+
A newly created global Operator conversation requests the native title
|
|
15
|
+
`Yui · Operator · MMdd`, using Yui's configured time zone. The request is made
|
|
16
|
+
only after the provider returns the exact new conversation identity; restore
|
|
17
|
+
and reconnect paths never replay it. Providers without a post-create rename
|
|
18
|
+
capability keep the usable Session and expose a diagnostic instead of treating
|
|
19
|
+
metadata failure as Session-creation failure.
|
|
20
|
+
|
|
14
21
|
## Authority
|
|
15
22
|
|
|
16
23
|
Task, WorkItem, Message, Decision, Artifact and Project Knowledge preserve
|
|
@@ -8,6 +8,11 @@ Provider binding,再结算执行占用。Global 停止证据保存为仅记录
|
|
|
8
8
|
不触发新的原生输入。待投递通知不能重启已明确停止的 Session;普通 Host 脱离仍
|
|
9
9
|
保留 active Session,可正常重连。部分切换失败后,仍须先结算旧输入再选择新会话。
|
|
10
10
|
|
|
11
|
+
新建 global Operator 对话会按 Yui 当前配置时区请求原生标题
|
|
12
|
+
`Yui · Operator · MMdd`。只有 Provider 返回新对话的确切身份后才执行该请求;
|
|
13
|
+
恢复与重连路径不会重放。若 Provider 不支持创建后改名,已创建的 Session 仍保持
|
|
14
|
+
可用,并留下诊断,而不会把元数据失败冒充为 Session 创建失败。
|
|
15
|
+
|
|
11
16
|
## 权威
|
|
12
17
|
|
|
13
18
|
Task、WorkItem、Message、Decision、Artifact 和 Project Knowledge 保存持久工作。
|
package/package.json
CHANGED
|
@@ -74,13 +74,30 @@ determine Task identity. They also do not determine WorkItem count. Let
|
|
|
74
74
|
isolated workspaces and Integration handle independent Git changes.
|
|
75
75
|
|
|
76
76
|
Keep the Task title concise and put detailed intent, constraints, and evidence
|
|
77
|
-
in its description or routed Message.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
in its description or routed Message.
|
|
78
|
+
|
|
79
|
+
Treat Task creation and Task submission as separate user-authorized actions.
|
|
80
|
+
When the user asks only to add, create, register, or record a Task, create the
|
|
81
|
+
Draft and write all known requirements into its metadata, then stop. Do not
|
|
82
|
+
submit a `discuss` or `develop` Message, request activation, start a Leader
|
|
83
|
+
Session, or create execution resources. A detailed or immediately actionable
|
|
84
|
+
requirement, or the user's desire for its eventual completion, is not permission
|
|
85
|
+
to start it. Report that the Task is a Draft and has not started, and explain
|
|
86
|
+
that the user can explicitly ask to discuss/plan it or to develop/implement it.
|
|
87
|
+
|
|
88
|
+
Start the corresponding route only when the user's current request contains
|
|
89
|
+
that additional intent: discussion or planning authorizes `discuss`;
|
|
90
|
+
development, implementation, fixing, continuing execution, or immediate
|
|
91
|
+
progress authorizes `develop` or the applicable lifecycle continuation. Do not
|
|
92
|
+
weaken explicit intent merely because the request also says “create a Task.”
|
|
93
|
+
The examples below are separate operations, never an automatic
|
|
94
|
+
create/submit/activate sequence.
|
|
95
|
+
|
|
96
|
+
Discussion does not authorize delivery; follow the Leader's
|
|
97
|
+
[planning and activation boundary](../yui-leader/references/planning.md) before
|
|
98
|
+
activation. Queries, record-only input and discussion do not authorize
|
|
99
|
+
reopening. An explicit request to continue the same completed result can
|
|
100
|
+
authorize the necessary reopening; follow the delivery guidance below.
|
|
84
101
|
|
|
85
102
|
```sh
|
|
86
103
|
yui operator submit "<related request and delta>" --task <task-id> --intent discuss
|
|
@@ -94,14 +111,17 @@ Inspect an existing request before creating another. `task activate <task-id>`
|
|
|
94
111
|
can adopt an eligible request in the foreground; it never activates a Draft
|
|
95
112
|
without a recorded request and environment plan.
|
|
96
113
|
|
|
97
|
-
|
|
98
|
-
text. `discuss` (the default
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
After the user has authorized a submission, choose its intent explicitly; it is
|
|
115
|
+
never inferred by the CLI from the message text. `discuss` (the CLI default when
|
|
116
|
+
`operator submit` is already being invoked) routes the Draft to planning;
|
|
117
|
+
`record` saves the message without waking the Leader; `develop` asks an unplanned
|
|
118
|
+
Draft to activate now, and reports the exact next step when it cannot (already
|
|
119
|
+
planned → request activation explicitly; execution stopped → start it first).
|
|
120
|
+
The CLI default is not permission for the Operator to invoke submission after a
|
|
121
|
+
creation-only request. Pass `--request-id <key>` to make a submission idempotent:
|
|
122
|
+
retrying the same key returns the original message and routing instead of
|
|
123
|
+
creating a duplicate, and the same key with different text is refused as a
|
|
124
|
+
conflict.
|
|
105
125
|
|
|
106
126
|
Resolve all known Projects before repository-backed execution. A stable Project
|
|
107
127
|
checkout is read-only reference state, not the Task base authority. Yui records
|