@things-factory/ai-assistant 10.1.154 → 10.1.155
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/client/bootstrap.ts +27 -0
- package/client/components/assistant-chat.ts +229 -178
- package/client/components/assistant-dock.ts +53 -48
- package/client/components/assistant-session-manager.ts +82 -100
- package/client/components/assistant-session-toolbar.ts +11 -3
- package/client/components/chat-input-builder.ts +8 -2
- package/client/settings/agent-settings-model.ts +164 -0
- package/client/settings/ai-agent-setting-let.ts +684 -0
- package/client/settings/ai-usage-setting-let.ts +252 -0
- package/client/settings/usage-model.ts +46 -0
- package/client/setup-assistant-dock.ts +2 -3
- package/client/utils/agent-choice.ts +56 -0
- package/client/utils/assistant-navigation.test.ts +14 -8
- package/client/utils/assistant-navigation.ts +8 -3
- package/client/utils/composer-frame.ts +28 -2
- package/client/utils/menu-room.ts +33 -0
- package/dist-client/bootstrap.d.ts +8 -0
- package/dist-client/bootstrap.js +26 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/components/assistant-chat.d.ts +32 -4
- package/dist-client/components/assistant-chat.js +234 -175
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.d.ts +6 -8
- package/dist-client/components/assistant-dock.js +52 -60
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-session-manager.d.ts +8 -7
- package/dist-client/components/assistant-session-manager.js +84 -124
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/components/assistant-session-toolbar.d.ts +1 -0
- package/dist-client/components/assistant-session-toolbar.js +10 -3
- package/dist-client/components/assistant-session-toolbar.js.map +1 -1
- package/dist-client/components/chat-input-builder.d.ts +4 -0
- package/dist-client/components/chat-input-builder.js +4 -2
- package/dist-client/components/chat-input-builder.js.map +1 -1
- package/dist-client/settings/agent-settings-model.d.ts +93 -0
- package/dist-client/settings/agent-settings-model.js +95 -0
- package/dist-client/settings/agent-settings-model.js.map +1 -0
- package/dist-client/settings/ai-agent-setting-let.d.ts +51 -0
- package/dist-client/settings/ai-agent-setting-let.js +672 -0
- package/dist-client/settings/ai-agent-setting-let.js.map +1 -0
- package/dist-client/settings/ai-usage-setting-let.d.ts +26 -0
- package/dist-client/settings/ai-usage-setting-let.js +276 -0
- package/dist-client/settings/ai-usage-setting-let.js.map +1 -0
- package/dist-client/settings/usage-model.d.ts +32 -0
- package/dist-client/settings/usage-model.js +25 -0
- package/dist-client/settings/usage-model.js.map +1 -0
- package/dist-client/setup-assistant-dock.d.ts +0 -1
- package/dist-client/setup-assistant-dock.js +0 -1
- package/dist-client/setup-assistant-dock.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/agent-choice.d.ts +24 -0
- package/dist-client/utils/agent-choice.js +38 -0
- package/dist-client/utils/agent-choice.js.map +1 -0
- package/dist-client/utils/assistant-navigation.d.ts +8 -3
- package/dist-client/utils/assistant-navigation.js +7 -2
- package/dist-client/utils/assistant-navigation.js.map +1 -1
- package/dist-client/utils/assistant-navigation.test.js +14 -8
- package/dist-client/utils/assistant-navigation.test.js.map +1 -1
- package/dist-client/utils/composer-frame.d.ts +23 -2
- package/dist-client/utils/composer-frame.js +20 -2
- package/dist-client/utils/composer-frame.js.map +1 -1
- package/dist-client/utils/menu-room.d.ts +13 -0
- package/dist-client/utils/menu-room.js +26 -0
- package/dist-client/utils/menu-room.js.map +1 -0
- package/dist-server/service/ai-agent-resolver.d.ts +82 -0
- package/dist-server/service/ai-agent-resolver.js +417 -0
- package/dist-server/service/ai-agent-resolver.js.map +1 -0
- package/dist-server/service/assistant-capability-resolver.d.ts +1 -1
- package/dist-server/service/assistant-capability-resolver.js +7 -7
- package/dist-server/service/assistant-capability-resolver.js.map +1 -1
- package/dist-server/service/assistant-chat-resolver.d.ts +2 -0
- package/dist-server/service/assistant-chat-resolver.js +32 -15
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/resolve.d.ts +0 -1
- package/dist-server/service/assistant-mode/resolve.js +0 -1
- package/dist-server/service/assistant-mode/resolve.js.map +1 -1
- package/dist-server/service/assistant-mode/types.d.ts +1 -2
- package/dist-server/service/assistant-mode/types.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +2 -0
- package/dist-server/service/chat-session/chat-session.js +5 -0
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -1
- package/dist-server/service/index.js +3 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/testing/mode-conformance.js +0 -1
- package/dist-server/service/testing/mode-conformance.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -7
- package/server/service/ai-agent-resolver.ts +330 -0
- package/server/service/assistant-capability-resolver.ts +7 -7
- package/server/service/assistant-chat-resolver.ts +32 -24
- package/server/service/assistant-mode/resolve.test.ts +0 -2
- package/server/service/assistant-mode/resolve.ts +0 -2
- package/server/service/assistant-mode/types.ts +1 -2
- package/server/service/chat-session/chat-session.ts +5 -0
- package/server/service/index.ts +3 -0
- package/server/service/testing/mode-conformance.ts +0 -1
- package/server/tool-registry-boot-report.test.ts +2 -0
- package/test/ai-agent-settings.test.ts +123 -0
- package/test/assistant-dock.test.ts +3 -2
- package/test/assistant-session-manager.test.ts +10 -4
- package/test/assistant-session-toolbar.test.ts +8 -0
- package/test/composer-frame.test.ts +40 -2
- package/test/menu-room.test.ts +33 -0
- package/test/open-doors.test.ts +3 -1
- package/test/session-discovery.test.ts +7 -6
- package/things-factory.config.js +2 -0
- package/translations/en.json +78 -12
- package/translations/es.json +78 -12
- package/translations/ja.json +78 -12
- package/translations/ko.json +78 -12
- package/translations/ms.json +78 -12
- package/translations/zh.json +78 -12
- package/client/components/chat-triggers.test.ts +0 -72
- package/client/components/chat-triggers.ts +0 -62
- package/dist-client/components/chat-triggers.d.ts +0 -16
- package/dist-client/components/chat-triggers.js +0 -42
- package/dist-client/components/chat-triggers.js.map +0 -1
- package/dist-client/components/chat-triggers.test.d.ts +0 -1
- package/dist-client/components/chat-triggers.test.js +0 -61
- package/dist-client/components/chat-triggers.test.js.map +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which AI agent the dock sends a turn with (design/ai/agent-settings.md §5).
|
|
3
|
+
*
|
|
4
|
+
* The server lists the agents a person may pick in this domain (`aiAgentChoices`); the dock sends the id of one, and
|
|
5
|
+
* the server decides the provider, the model and the key from it. What a person last picked is a convenience of this
|
|
6
|
+
* browser, kept per domain: an id the list no longer has is dropped and the domain's default answers.
|
|
7
|
+
*/
|
|
8
|
+
export interface AgentChoice {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
provider: string;
|
|
12
|
+
model: string;
|
|
13
|
+
isDefault: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** The agent to send with: the remembered one while the domain still offers it, else the default, else the first. */
|
|
16
|
+
export declare function pickAgentId(choices: AgentChoice[], remembered?: string | null): string | undefined;
|
|
17
|
+
/** A picker is drawn only when there is something to pick (the same rule as the mode picker, ADR-0072). */
|
|
18
|
+
export declare function offersAgentPicker(choices: AgentChoice[]): boolean;
|
|
19
|
+
export declare function agentMemoryKey(contextPath: string | undefined): string;
|
|
20
|
+
export declare function rememberedAgent(contextPath: string | undefined): string | undefined;
|
|
21
|
+
export declare function rememberAgent(contextPath: string | undefined, agentId: string): void;
|
|
22
|
+
/** `Claude · claude-sonnet-4-6` - what the picker shows beside a name. */
|
|
23
|
+
export declare function agentDetail(choice: Pick<AgentChoice, 'provider' | 'model'>): string;
|
|
24
|
+
export declare function providerLabel(provider: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The agent to send with: the remembered one while the domain still offers it, else the default, else the first. */
|
|
2
|
+
export function pickAgentId(choices, remembered) {
|
|
3
|
+
if (remembered && choices.some(one => one.id === remembered))
|
|
4
|
+
return remembered;
|
|
5
|
+
return (choices.find(one => one.isDefault) ?? choices[0])?.id;
|
|
6
|
+
}
|
|
7
|
+
/** A picker is drawn only when there is something to pick (the same rule as the mode picker, ADR-0072). */
|
|
8
|
+
export function offersAgentPicker(choices) {
|
|
9
|
+
return choices.length >= 2;
|
|
10
|
+
}
|
|
11
|
+
export function agentMemoryKey(contextPath) {
|
|
12
|
+
return `assistant-agent:${contextPath ?? ''}`;
|
|
13
|
+
}
|
|
14
|
+
export function rememberedAgent(contextPath) {
|
|
15
|
+
try {
|
|
16
|
+
return localStorage.getItem(agentMemoryKey(contextPath)) ?? undefined;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function rememberAgent(contextPath, agentId) {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(agentMemoryKey(contextPath), agentId);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* A browser that blocks storage still picks - it just asks again next time. */
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const PROVIDER_LABEL = { openai: 'OpenAI', anthropic: 'Claude', gemini: 'Gemini' };
|
|
31
|
+
/** `Claude · claude-sonnet-4-6` - what the picker shows beside a name. */
|
|
32
|
+
export function agentDetail(choice) {
|
|
33
|
+
return `${PROVIDER_LABEL[choice.provider] ?? choice.provider} · ${choice.model}`;
|
|
34
|
+
}
|
|
35
|
+
export function providerLabel(provider) {
|
|
36
|
+
return PROVIDER_LABEL[provider] ?? provider;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=agent-choice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-choice.js","sourceRoot":"","sources":["../../client/utils/agent-choice.ts"],"names":[],"mappings":"AAeA,qHAAqH;AACrH,MAAM,UAAU,WAAW,CAAC,OAAsB,EAAE,UAA0B;IAC5E,IAAI,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,UAAU,CAAC;QAAE,OAAO,UAAU,CAAA;IAC/E,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;AAC/D,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,iBAAiB,CAAC,OAAsB;IACtD,OAAO,OAAO,CAAC,MAAM,IAAI,CAAC,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,WAA+B;IAC5D,OAAO,mBAAmB,WAAW,IAAI,EAAE,EAAE,CAAA;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAA+B;IAC7D,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,IAAI,SAAS,CAAA;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,WAA+B,EAAE,OAAe;IAC5E,IAAI,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAA;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,+EAA+E;IACjF,CAAC;AACH,CAAC;AAED,MAAM,cAAc,GAA2B,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;AAE1G,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,MAA+C;IACzE,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;AAClF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAA;AAC7C,CAAC","sourcesContent":["/**\n * Which AI agent the dock sends a turn with (design/ai/agent-settings.md §5).\n *\n * The server lists the agents a person may pick in this domain (`aiAgentChoices`); the dock sends the id of one, and\n * the server decides the provider, the model and the key from it. What a person last picked is a convenience of this\n * browser, kept per domain: an id the list no longer has is dropped and the domain's default answers.\n */\nexport interface AgentChoice {\n id: string\n name: string\n provider: string\n model: string\n isDefault: boolean\n}\n\n/** The agent to send with: the remembered one while the domain still offers it, else the default, else the first. */\nexport function pickAgentId(choices: AgentChoice[], remembered?: string | null): string | undefined {\n if (remembered && choices.some(one => one.id === remembered)) return remembered\n return (choices.find(one => one.isDefault) ?? choices[0])?.id\n}\n\n/** A picker is drawn only when there is something to pick (the same rule as the mode picker, ADR-0072). */\nexport function offersAgentPicker(choices: AgentChoice[]): boolean {\n return choices.length >= 2\n}\n\nexport function agentMemoryKey(contextPath: string | undefined): string {\n return `assistant-agent:${contextPath ?? ''}`\n}\n\nexport function rememberedAgent(contextPath: string | undefined): string | undefined {\n try {\n return localStorage.getItem(agentMemoryKey(contextPath)) ?? undefined\n } catch {\n return undefined\n }\n}\n\nexport function rememberAgent(contextPath: string | undefined, agentId: string): void {\n try {\n localStorage.setItem(agentMemoryKey(contextPath), agentId)\n } catch {\n /* A browser that blocks storage still picks - it just asks again next time. */\n }\n}\n\nconst PROVIDER_LABEL: Record<string, string> = { openai: 'OpenAI', anthropic: 'Claude', gemini: 'Gemini' }\n\n/** `Claude · claude-sonnet-4-6` - what the picker shows beside a name. */\nexport function agentDetail(choice: Pick<AgentChoice, 'provider' | 'model'>): string {\n return `${PROVIDER_LABEL[choice.provider] ?? choice.provider} · ${choice.model}`\n}\n\nexport function providerLabel(provider: string): string {\n return PROVIDER_LABEL[provider] ?? provider\n}\n"]}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The page decides the conversation (design/ai/shared-dock.md §2-3, 2026-10-12): moving to another screen moves the
|
|
3
|
+
* dock to that screen's conversation, with no choice put to the person. What was being written stays with its subject
|
|
4
|
+
* and comes back on return. An answer in progress finishes first - then the dock follows. An identity change never
|
|
5
|
+
* carries the old conversation.
|
|
6
|
+
*/
|
|
2
7
|
export declare function surfaceTransition(input: {
|
|
3
8
|
identityChanged: boolean;
|
|
4
9
|
sameSubject: boolean;
|
|
5
|
-
|
|
6
|
-
}): 'reset' | 'refresh' | '
|
|
10
|
+
busy: boolean;
|
|
11
|
+
}): 'reset' | 'refresh' | 'wait' | 'follow';
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The page decides the conversation (design/ai/shared-dock.md §2-3, 2026-10-12): moving to another screen moves the
|
|
3
|
+
* dock to that screen's conversation, with no choice put to the person. What was being written stays with its subject
|
|
4
|
+
* and comes back on return. An answer in progress finishes first - then the dock follows. An identity change never
|
|
5
|
+
* carries the old conversation.
|
|
6
|
+
*/
|
|
2
7
|
export function surfaceTransition(input) {
|
|
3
8
|
if (input.identityChanged)
|
|
4
9
|
return 'reset';
|
|
5
10
|
if (input.sameSubject)
|
|
6
11
|
return 'refresh';
|
|
7
|
-
return input.
|
|
12
|
+
return input.busy ? 'wait' : 'follow';
|
|
8
13
|
}
|
|
9
14
|
//# sourceMappingURL=assistant-navigation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-navigation.js","sourceRoot":"","sources":["../../client/utils/assistant-navigation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"assistant-navigation.js","sourceRoot":"","sources":["../../client/utils/assistant-navigation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAwE;IACxG,IAAI,KAAK,CAAC,eAAe;QAAE,OAAO,OAAO,CAAA;IACzC,IAAI,KAAK,CAAC,WAAW;QAAE,OAAO,SAAS,CAAA;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;AACvC,CAAC","sourcesContent":["/**\n * The page decides the conversation (design/ai/shared-dock.md §2-3, 2026-10-12): moving to another screen moves the\n * dock to that screen's conversation, with no choice put to the person. What was being written stays with its subject\n * and comes back on return. An answer in progress finishes first - then the dock follows. An identity change never\n * carries the old conversation.\n */\nexport function surfaceTransition(input: { identityChanged: boolean; sameSubject: boolean; busy: boolean }): 'reset' | 'refresh' | 'wait' | 'follow' {\n if (input.identityChanged) return 'reset'\n if (input.sameSubject) return 'refresh'\n return input.busy ? 'wait' : 'follow'\n}\n"]}
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import { surfaceTransition } from './assistant-navigation.js';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
describe('
|
|
5
|
-
test.each([false, true])('a different identity resets even when
|
|
4
|
+
describe('the page decides the conversation - no choice is put to the person', () => {
|
|
5
|
+
test.each([false, true])('a different identity resets even when busy=%s', busy => {
|
|
6
6
|
for (const sameSubject of [false, true]) {
|
|
7
|
-
expect(surfaceTransition({ identityChanged: true, sameSubject,
|
|
7
|
+
expect(surfaceTransition({ identityChanged: true, sameSubject, busy })).toBe('reset');
|
|
8
8
|
}
|
|
9
9
|
});
|
|
10
|
-
test('
|
|
11
|
-
expect(surfaceTransition({ identityChanged: false, sameSubject: false,
|
|
10
|
+
test('★ another screen is followed - a conversation in progress or text being written does not hold the dock', () => {
|
|
11
|
+
expect(surfaceTransition({ identityChanged: false, sameSubject: false, busy: false })).toBe('follow');
|
|
12
12
|
});
|
|
13
|
-
test('an
|
|
14
|
-
expect(surfaceTransition({ identityChanged: false, sameSubject: false,
|
|
13
|
+
test('★ an answer in progress finishes in its own conversation first, then the dock follows', () => {
|
|
14
|
+
expect(surfaceTransition({ identityChanged: false, sameSubject: false, busy: true })).toBe('wait');
|
|
15
15
|
});
|
|
16
16
|
test('returning to the same subject refreshes without reopening', () => {
|
|
17
|
-
expect(surfaceTransition({ identityChanged: false, sameSubject: true,
|
|
17
|
+
expect(surfaceTransition({ identityChanged: false, sameSubject: true, busy: true })).toBe('refresh');
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
20
|
describe('navigation wiring', () => {
|
|
21
21
|
const dock = readFileSync(join(__dirname, '../components/assistant-dock.ts'), 'utf8');
|
|
22
|
+
test('★ what was being written stays with its subject and comes back on return', () => {
|
|
23
|
+
expect(dock).toMatch(/if \(transition !== 'reset'\) this\.keepDraft\(\)/);
|
|
24
|
+
expect(dock).toContain('void this.restoreDraft()');
|
|
25
|
+
expect(dock).not.toContain('screenChanged');
|
|
26
|
+
expect(dock).not.toContain('switch-to-screen');
|
|
27
|
+
});
|
|
22
28
|
test('history hides rather than unmounts the chat and its unsent input', () => {
|
|
23
29
|
expect(dock).toContain('?hidden=${this.showingHistory}');
|
|
24
30
|
expect(dock).toContain('ready && !this.loading && !this.failure');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-navigation.test.js","sourceRoot":"","sources":["../../client/utils/assistant-navigation.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,QAAQ,CAAC,oEAAoE,EAAE,GAAG,EAAE;IAClF,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"assistant-navigation.test.js","sourceRoot":"","sources":["../../client/utils/assistant-navigation.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { surfaceTransition } from './assistant-navigation.js'\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\ndescribe('the page decides the conversation - no choice is put to the person', () => {\n test.each([false, true])('a different identity resets even when busy=%s', busy => {\n for (const sameSubject of [false, true]) {\n expect(surfaceTransition({ identityChanged: true, sameSubject, busy })).toBe('reset')\n }\n })\n test('★ another screen is followed - a conversation in progress or text being written does not hold the dock', () => {\n expect(surfaceTransition({ identityChanged: false, sameSubject: false, busy: false })).toBe('follow')\n })\n test('★ an answer in progress finishes in its own conversation first, then the dock follows', () => {\n expect(surfaceTransition({ identityChanged: false, sameSubject: false, busy: true })).toBe('wait')\n })\n test('returning to the same subject refreshes without reopening', () => {\n expect(surfaceTransition({ identityChanged: false, sameSubject: true, busy: true })).toBe('refresh')\n })\n})\n\ndescribe('navigation wiring', () => {\n const dock = readFileSync(join(__dirname, '../components/assistant-dock.ts'), 'utf8')\n test('★ what was being written stays with its subject and comes back on return', () => {\n expect(dock).toMatch(/if \\(transition !== 'reset'\\) this\\.keepDraft\\(\\)/)\n expect(dock).toContain('void this.restoreDraft()')\n expect(dock).not.toContain('screenChanged')\n expect(dock).not.toContain('switch-to-screen')\n })\n test('history hides rather than unmounts the chat and its unsent input', () => {\n expect(dock).toContain('?hidden=${this.showingHistory}')\n expect(dock).toContain('ready && !this.loading && !this.failure')\n })\n test('identity changes clear a carried ask and reread capability', () => {\n expect(dock).toContain(\"if (transition === 'reset')\")\n expect(dock).toContain('this.asking = undefined')\n expect(dock).toContain(\"this.capability = 'unknown'\")\n })\n test('all direct board actions check the current screen, not the retained editor', () => {\n expect(dock).toContain('if (!this.mayApplyOnScreen()) return')\n expect(dock.match(/if \\(this.mayApplyOnScreen\\(\\)\\)/g)).toHaveLength(2)\n expect(dock).toContain(\"errorCode: 'conversation-screen-changed'\")\n })\n})\n\n\ndescribe('a carried draft survives opening a new conversation', () => {\n test('automatic review starts after cleanup and is sent once per request', () => {\n const source = readFileSync(join(__dirname, '../components/assistant-chat.ts'), 'utf8')\n const update = source.slice(source.indexOf(' updated(changed:'), source.indexOf(' disconnectedCallback()', source.indexOf(' updated(changed:')))\n const placement = update.indexOf('if (this.autoAsk &&')\n expect(placement).toBeGreaterThan(update.indexOf(\"if (changed.has('sessionId')\"))\n expect(update.slice(placement)).not.toContain(\"this.input = ''\")\n expect(update.slice(placement)).toContain('!== this._autoAsked')\n expect(update.slice(placement)).toContain('void this.send()')\n })\n test('draft placement follows subject, mode and session cleanup', () => {\n const source = readFileSync(join(__dirname, '../components/assistant-chat.ts'), 'utf8')\n const update = source.slice(source.indexOf(' updated(changed:'), source.indexOf(' disconnectedCallback()', source.indexOf(' updated(changed:')))\n const placement = update.indexOf('const draft = assistantDraftDecision(')\n expect(placement).toBeGreaterThan(update.indexOf(\"if (changed.has('subject'))\"))\n expect(placement).toBeGreaterThan(update.indexOf(\"if (changed.has('mode')\"))\n expect(placement).toBeGreaterThan(update.indexOf(\"if (changed.has('sessionId')\"))\n expect(update.slice(placement)).not.toContain(\"this.input = ''\")\n })\n})\n"]}
|
|
@@ -12,8 +12,15 @@ export type ComposerKey = {
|
|
|
12
12
|
metaKey?: boolean;
|
|
13
13
|
altKey?: boolean;
|
|
14
14
|
};
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* What a key does in the box: send, close the wide box, take the suggestion, or nothing of ours (the textarea keeps
|
|
17
|
+
* it). The words shown in an empty box are a request the screen suggests - a plain Tab writes them in, so the person
|
|
18
|
+
* does not have to type what the screen already said.
|
|
19
|
+
*/
|
|
20
|
+
export declare function composerKeyAction(e: ComposerKey, wide: boolean, box?: {
|
|
21
|
+
empty?: boolean;
|
|
22
|
+
suggestion?: string;
|
|
23
|
+
}): 'send' | 'narrow' | 'take-suggestion' | null;
|
|
17
24
|
/** The shortcut the wide box's send button names, in the keys of this machine. */
|
|
18
25
|
export declare function sendShortcut(platform: string | undefined): string;
|
|
19
26
|
/**
|
|
@@ -26,3 +33,17 @@ export declare function tableAsWords(table: {
|
|
|
26
33
|
}): string;
|
|
27
34
|
/** The words appended to what is already written - on a line of their own. */
|
|
28
35
|
export declare function appendWords(written: string, words: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* The request the empty box suggests, which a Tab writes in: the conversation's first example request, its own
|
|
38
|
+
* before the host's. A placeholder is how to write, not a request, so it is never the suggestion. Once the
|
|
39
|
+
* conversation is under way an opening example no longer fits, and nothing is suggested.
|
|
40
|
+
*/
|
|
41
|
+
export declare function composerSuggestion(input: {
|
|
42
|
+
started: boolean;
|
|
43
|
+
modeExamples?: Array<{
|
|
44
|
+
items: string[];
|
|
45
|
+
}>;
|
|
46
|
+
hostExamples?: Array<{
|
|
47
|
+
items: string[];
|
|
48
|
+
}>;
|
|
49
|
+
}): string | undefined;
|
|
@@ -15,8 +15,15 @@ export function composerHeight(content, chat, resting) {
|
|
|
15
15
|
const cap = Math.max(resting, Math.floor(chat * COMPOSER_GROWTH));
|
|
16
16
|
return { height: Math.min(Math.max(content, resting), cap), scrolls: content > cap };
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* What a key does in the box: send, close the wide box, take the suggestion, or nothing of ours (the textarea keeps
|
|
20
|
+
* it). The words shown in an empty box are a request the screen suggests - a plain Tab writes them in, so the person
|
|
21
|
+
* does not have to type what the screen already said.
|
|
22
|
+
*/
|
|
23
|
+
export function composerKeyAction(e, wide, box = {}) {
|
|
24
|
+
if (e.key === 'Tab' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && box.empty && box.suggestion?.trim()) {
|
|
25
|
+
return 'take-suggestion';
|
|
26
|
+
}
|
|
20
27
|
if (e.key === 'Escape')
|
|
21
28
|
return wide ? 'narrow' : null;
|
|
22
29
|
if (e.key !== 'Enter' || e.altKey)
|
|
@@ -43,4 +50,15 @@ export function tableAsWords(table) {
|
|
|
43
50
|
export function appendWords(written, words) {
|
|
44
51
|
return written.trim() ? `${written.replace(/\s+$/, '')}\n${words}` : words;
|
|
45
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The request the empty box suggests, which a Tab writes in: the conversation's first example request, its own
|
|
55
|
+
* before the host's. A placeholder is how to write, not a request, so it is never the suggestion. Once the
|
|
56
|
+
* conversation is under way an opening example no longer fits, and nothing is suggested.
|
|
57
|
+
*/
|
|
58
|
+
export function composerSuggestion(input) {
|
|
59
|
+
if (input.started)
|
|
60
|
+
return undefined;
|
|
61
|
+
const groups = input.modeExamples?.length ? input.modeExamples : input.hostExamples;
|
|
62
|
+
return groups?.find(group => group.items.length > 0)?.items[0];
|
|
63
|
+
}
|
|
46
64
|
//# sourceMappingURL=composer-frame.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer-frame.js","sourceRoot":"","sources":["../../client/utils/composer-frame.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAElC,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY,EAAE,OAAe;IAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,eAAe,CAAC,CAAC,CAAA;IACjE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,GAAG,EAAE,CAAA;AACtF,CAAC;AAID
|
|
1
|
+
{"version":3,"file":"composer-frame.js","sourceRoot":"","sources":["../../client/utils/composer-frame.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The composer's frame (operato-application/design/ai/shared-dock.md §10) - how tall it stands, what Enter does,\n * and what a pasted table gives back as words. Pure: the chat hands in its measures and the key.\n *\n * - The box grows with its text up to 40% of the chat's height, then scrolls. It never stands under its resting\n * height, and on a chat too short for 40% to hold that height the resting height wins.\n * - The wide box (넓게 쓰기) covers the conversation inside the dock; the page beside it stays where it was.\n * - In the small box Enter sends and Shift+Enter breaks the line, as in every chat. In the wide box Enter breaks\n * the line - long writing is made of lines - and Ctrl/Cmd+Enter sends. Ctrl/Cmd+Enter sends in both.\n */\n\n/** The part of the chat's height the small box may take before it scrolls. */\nexport const COMPOSER_GROWTH = 0.4\n\n/** The box's height for its content, both border-box pixels. */\nexport function composerHeight(content: number, chat: number, resting: number): { height: number; scrolls: boolean } {\n const cap = Math.max(resting, Math.floor(chat * COMPOSER_GROWTH))\n return { height: Math.min(Math.max(content, resting), cap), scrolls: content > cap }\n}\n\nexport type ComposerKey = { key: string; shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; altKey?: boolean }\n\n/**\n * What a key does in the box: send, close the wide box, take the suggestion, or nothing of ours (the textarea keeps\n * it). The words shown in an empty box are a request the screen suggests - a plain Tab writes them in, so the person\n * does not have to type what the screen already said.\n */\nexport function composerKeyAction(\n e: ComposerKey,\n wide: boolean,\n box: { empty?: boolean; suggestion?: string } = {}\n): 'send' | 'narrow' | 'take-suggestion' | null {\n if (e.key === 'Tab' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey && box.empty && box.suggestion?.trim()) {\n return 'take-suggestion'\n }\n if (e.key === 'Escape') return wide ? 'narrow' : null\n if (e.key !== 'Enter' || e.altKey) return null\n if (e.ctrlKey || e.metaKey) return 'send'\n if (wide || e.shiftKey) return null\n return 'send'\n}\n\n/** The shortcut the wide box's send button names, in the keys of this machine. */\nexport function sendShortcut(platform: string | undefined): string {\n return /mac|iphone|ipad/i.test(platform ?? '') ? '⌘ Enter' : 'Ctrl+Enter'\n}\n\n/**\n * A pasted table given back as words: what the clipboard held as text, or - when it held only HTML - the rows with\n * a tab between cells, as a spreadsheet copies them.\n */\nexport function tableAsWords(table: { rows: string[][]; text?: string | null }): string {\n const text = (table.text ?? '').replace(/\\r\\n?/g, '\\n').replace(/\\n+$/, '')\n return text.trim() ? text : table.rows.map(row => row.join('\\t')).join('\\n')\n}\n\n/** The words appended to what is already written - on a line of their own. */\nexport function appendWords(written: string, words: string): string {\n return written.trim() ? `${written.replace(/\\s+$/, '')}\\n${words}` : words\n}\n\n/**\n * The request the empty box suggests, which a Tab writes in: the conversation's first example request, its own\n * before the host's. A placeholder is how to write, not a request, so it is never the suggestion. Once the\n * conversation is under way an opening example no longer fits, and nothing is suggested.\n */\nexport function composerSuggestion(input: {\n started: boolean\n modeExamples?: Array<{ items: string[] }>\n hostExamples?: Array<{ items: string[] }>\n}): string | undefined {\n if (input.started) return undefined\n const groups = input.modeExamples?.length ? input.modeExamples : input.hostExamples\n return groups?.find(group => group.items.length > 0)?.items[0]\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page's menu gives the dock its room (2026-10-12). When the dock opens beside the page, the left menu folds to its
|
|
3
|
+
* icons; when the dock closes, the menu opens again - only if it is still folded the way the dock left it. A menu the
|
|
4
|
+
* person folded themselves stays folded, and the dock's fold is never saved as the person's choice: it sets the menu's
|
|
5
|
+
* state without the menu's own `setCollapsed`, which stores it.
|
|
6
|
+
*/
|
|
7
|
+
export interface MenuLike {
|
|
8
|
+
collapsed: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function foldForDock(menu: MenuLike | undefined): boolean;
|
|
11
|
+
export declare function unfoldAfterDock(menu: MenuLike | undefined, foldedByDock: boolean): void;
|
|
12
|
+
/** The framework's menu (`ox-menu-part` of @operato/menu), wherever the layout put it. */
|
|
13
|
+
export declare function findMenu(root?: Document | ShadowRoot): (HTMLElement & MenuLike) | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function foldForDock(menu) {
|
|
2
|
+
if (!menu || menu.collapsed)
|
|
3
|
+
return false;
|
|
4
|
+
menu.collapsed = true;
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
export function unfoldAfterDock(menu, foldedByDock) {
|
|
8
|
+
if (!menu || !foldedByDock || !menu.collapsed)
|
|
9
|
+
return;
|
|
10
|
+
menu.collapsed = false;
|
|
11
|
+
}
|
|
12
|
+
/** The framework's menu (`ox-menu-part` of @operato/menu), wherever the layout put it. */
|
|
13
|
+
export function findMenu(root = document) {
|
|
14
|
+
const found = root.querySelector('ox-menu-part');
|
|
15
|
+
if (found)
|
|
16
|
+
return found;
|
|
17
|
+
for (const element of root.querySelectorAll('*')) {
|
|
18
|
+
if (element.shadowRoot) {
|
|
19
|
+
const inner = findMenu(element.shadowRoot);
|
|
20
|
+
if (inner)
|
|
21
|
+
return inner;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=menu-room.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-room.js","sourceRoot":"","sources":["../../client/utils/menu-room.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,WAAW,CAAC,IAA0B;IACpD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAA;IACzC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACrB,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAA0B,EAAE,YAAqB;IAC/E,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,OAAM;IACrD,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;AACxB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,QAAQ,CAAC,OAA8B,QAAQ;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,CAAoC,CAAA;IACnF,IAAI,KAAK;QAAE,OAAO,KAAK,CAAA;IACvB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;YAC1C,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAA;QACzB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC","sourcesContent":["/**\n * The page's menu gives the dock its room (2026-10-12). When the dock opens beside the page, the left menu folds to its\n * icons; when the dock closes, the menu opens again - only if it is still folded the way the dock left it. A menu the\n * person folded themselves stays folded, and the dock's fold is never saved as the person's choice: it sets the menu's\n * state without the menu's own `setCollapsed`, which stores it.\n */\nexport interface MenuLike {\n collapsed: boolean\n}\n\nexport function foldForDock(menu: MenuLike | undefined): boolean {\n if (!menu || menu.collapsed) return false\n menu.collapsed = true\n return true\n}\n\nexport function unfoldAfterDock(menu: MenuLike | undefined, foldedByDock: boolean): void {\n if (!menu || !foldedByDock || !menu.collapsed) return\n menu.collapsed = false\n}\n\n/** The framework's menu (`ox-menu-part` of @operato/menu), wherever the layout put it. */\nexport function findMenu(root: Document | ShadowRoot = document): (HTMLElement & MenuLike) | undefined {\n const found = root.querySelector('ox-menu-part') as (HTMLElement & MenuLike) | null\n if (found) return found\n for (const element of root.querySelectorAll('*')) {\n if (element.shadowRoot) {\n const inner = findMenu(element.shadowRoot)\n if (inner) return inner\n }\n }\n return undefined\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import '@things-factory/auth-base';
|
|
2
|
+
declare class AiAgentDomain {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
declare class AiAgentView {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
provider: string;
|
|
10
|
+
model: string;
|
|
11
|
+
baseURL?: string;
|
|
12
|
+
organization?: string;
|
|
13
|
+
active: boolean;
|
|
14
|
+
owner: AiAgentDomain;
|
|
15
|
+
inherited: boolean;
|
|
16
|
+
turnedOff: boolean;
|
|
17
|
+
keyState: string;
|
|
18
|
+
keyDomain?: AiAgentDomain;
|
|
19
|
+
keyHint?: string;
|
|
20
|
+
usable: boolean;
|
|
21
|
+
isDefault: boolean;
|
|
22
|
+
usedBelow: AiAgentDomain[];
|
|
23
|
+
}
|
|
24
|
+
declare class AiAgentSettings {
|
|
25
|
+
chain: AiAgentDomain[];
|
|
26
|
+
agents: AiAgentView[];
|
|
27
|
+
defaultAgentId?: string;
|
|
28
|
+
defaultFrom: string;
|
|
29
|
+
state: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
declare class AiAgentChoice {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
provider: string;
|
|
36
|
+
model: string;
|
|
37
|
+
isDefault: boolean;
|
|
38
|
+
}
|
|
39
|
+
declare class AiAgentInput {
|
|
40
|
+
id?: string;
|
|
41
|
+
name: string;
|
|
42
|
+
provider: string;
|
|
43
|
+
model: string;
|
|
44
|
+
baseURL?: string;
|
|
45
|
+
organization?: string;
|
|
46
|
+
active: boolean;
|
|
47
|
+
apiKey?: string;
|
|
48
|
+
}
|
|
49
|
+
declare class InheritedKeyInput {
|
|
50
|
+
agentId: string;
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
clear?: boolean;
|
|
53
|
+
}
|
|
54
|
+
declare class AiAgentsInput {
|
|
55
|
+
agents: AiAgentInput[];
|
|
56
|
+
defaultAgentId?: string;
|
|
57
|
+
turnedOff?: string[];
|
|
58
|
+
inheritedKeys?: InheritedKeyInput[];
|
|
59
|
+
}
|
|
60
|
+
declare class AiUsageBucket {
|
|
61
|
+
bucket: string;
|
|
62
|
+
agentId?: string;
|
|
63
|
+
agentName?: string;
|
|
64
|
+
provider: string;
|
|
65
|
+
model: string;
|
|
66
|
+
domainId: string;
|
|
67
|
+
domainName: string;
|
|
68
|
+
userId?: string;
|
|
69
|
+
userName?: string;
|
|
70
|
+
calls: number;
|
|
71
|
+
inputTokens: number;
|
|
72
|
+
outputTokens: number;
|
|
73
|
+
failures: number;
|
|
74
|
+
}
|
|
75
|
+
export declare class AiAgentResolver {
|
|
76
|
+
aiAgentSettings(context: ResolverContext): Promise<AiAgentSettings>;
|
|
77
|
+
aiAgentChoices(context: ResolverContext): Promise<AiAgentChoice[]>;
|
|
78
|
+
aiProviderModels(provider: string, apiKey: string, agentId: string, baseURL: string, organization: string, context: ResolverContext): Promise<string[]>;
|
|
79
|
+
saveAiAgents(input: AiAgentsInput, context: ResolverContext): Promise<boolean>;
|
|
80
|
+
aiUsageSummary(from: Date, to: Date, bucket: string, agentIds: string[], userIds: string[], includeDescendants: boolean, context: ResolverContext): Promise<AiUsageBucket[]>;
|
|
81
|
+
}
|
|
82
|
+
export {};
|