ticlawk 0.1.16-dev.10 → 0.1.16-dev.12
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/bin/ticlawk.mjs +117 -0
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +189 -0
- package/src/adapters/ticlawk/index.mjs +63 -4
- package/src/cli/agent-commands.mjs +397 -1
- package/src/core/agent-cli-handlers.mjs +300 -0
- package/src/core/agent-home.mjs +45 -1
- package/src/core/argv.mjs +11 -1
- package/src/core/http.mjs +102 -0
- package/src/runtimes/_shared/standing-prompt.mjs +2 -0
package/bin/ticlawk.mjs
CHANGED
|
@@ -32,6 +32,22 @@ import {
|
|
|
32
32
|
AGENT_COMMAND_HELP,
|
|
33
33
|
runAttachmentViewCommand,
|
|
34
34
|
runGroupCreateCommand,
|
|
35
|
+
runWorkstreamCharterGetCommand,
|
|
36
|
+
runWorkstreamCharterSetCommand,
|
|
37
|
+
runWorkstreamCreateCommand,
|
|
38
|
+
runWorkstreamDeleteCommand,
|
|
39
|
+
runWorkstreamListCommand,
|
|
40
|
+
runAgentCreateCommand,
|
|
41
|
+
runAgentDeleteCommand,
|
|
42
|
+
runDashboardSetCommand,
|
|
43
|
+
runDashboardGetCommand,
|
|
44
|
+
runCredentialRequestCommand,
|
|
45
|
+
runServiceCreateCommand,
|
|
46
|
+
runServiceUpdateCommand,
|
|
47
|
+
runServiceDeleteCommand,
|
|
48
|
+
runServiceListCommand,
|
|
49
|
+
runServiceInfoCommand,
|
|
50
|
+
runServiceCallCommand,
|
|
35
51
|
runGroupMembersAddCommand,
|
|
36
52
|
runGroupMembersCommand,
|
|
37
53
|
runGroupMembersRemoveCommand,
|
|
@@ -453,6 +469,107 @@ async function main() {
|
|
|
453
469
|
process.exit(1);
|
|
454
470
|
}
|
|
455
471
|
|
|
472
|
+
if (command === 'workstream') {
|
|
473
|
+
const sub = args._[1];
|
|
474
|
+
if (args.help || args.h || !sub) {
|
|
475
|
+
console.log(AGENT_COMMAND_HELP.workstream);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (sub === 'create') {
|
|
479
|
+
process.exitCode = await runWorkstreamCreateCommand(args);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (sub === 'delete') {
|
|
483
|
+
process.exitCode = await runWorkstreamDeleteCommand(args);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (sub === 'list') {
|
|
487
|
+
process.exitCode = await runWorkstreamListCommand(args);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (sub === 'charter') {
|
|
491
|
+
const op = args._[2];
|
|
492
|
+
if (op === 'get') {
|
|
493
|
+
process.exitCode = await runWorkstreamCharterGetCommand(args);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (op === 'set') {
|
|
497
|
+
process.exitCode = await runWorkstreamCharterSetCommand(args);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
console.error(`unknown workstream charter op: ${op}`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
console.error(`unknown workstream subcommand: ${sub}`);
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (command === 'agent') {
|
|
508
|
+
const sub = args._[1];
|
|
509
|
+
if (args.help || args.h || !sub) {
|
|
510
|
+
console.log(AGENT_COMMAND_HELP.agent);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (sub === 'create') {
|
|
514
|
+
process.exitCode = await runAgentCreateCommand(args);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (sub === 'delete') {
|
|
518
|
+
process.exitCode = await runAgentDeleteCommand(args);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
console.error(`unknown agent subcommand: ${sub}`);
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (command === 'service') {
|
|
526
|
+
const sub = args._[1];
|
|
527
|
+
if (args.help || args.h || !sub) {
|
|
528
|
+
console.log(AGENT_COMMAND_HELP.service);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (sub === 'create') { process.exitCode = await runServiceCreateCommand(args); return; }
|
|
532
|
+
if (sub === 'update') { process.exitCode = await runServiceUpdateCommand(args); return; }
|
|
533
|
+
if (sub === 'delete') { process.exitCode = await runServiceDeleteCommand(args); return; }
|
|
534
|
+
if (sub === 'list') { process.exitCode = await runServiceListCommand(args); return; }
|
|
535
|
+
if (sub === 'info') { process.exitCode = await runServiceInfoCommand(args); return; }
|
|
536
|
+
if (sub === 'call') { process.exitCode = await runServiceCallCommand(args); return; }
|
|
537
|
+
console.error(`unknown service subcommand: ${sub}`);
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (command === 'credential') {
|
|
542
|
+
const sub = args._[1];
|
|
543
|
+
if (args.help || args.h || !sub) {
|
|
544
|
+
console.log(AGENT_COMMAND_HELP.credential);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (sub === 'request') {
|
|
548
|
+
process.exitCode = await runCredentialRequestCommand(args);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
console.error(`unknown credential subcommand: ${sub}`);
|
|
552
|
+
process.exit(1);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (command === 'dashboard') {
|
|
556
|
+
const sub = args._[1];
|
|
557
|
+
if (args.help || args.h || !sub) {
|
|
558
|
+
console.log(AGENT_COMMAND_HELP.dashboard);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (sub === 'set') {
|
|
562
|
+
process.exitCode = await runDashboardSetCommand(args);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (sub === 'get') {
|
|
566
|
+
process.exitCode = await runDashboardGetCommand(args);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
console.error(`unknown dashboard subcommand: ${sub}`);
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
|
|
456
573
|
if (command === 'group') {
|
|
457
574
|
const sub = args._[1];
|
|
458
575
|
if (args.help || args.h || !sub) {
|
package/package.json
CHANGED
|
@@ -188,6 +188,7 @@ export async function sendAgentMessage({
|
|
|
188
188
|
runtimeHostId,
|
|
189
189
|
visibility,
|
|
190
190
|
mediaAssetIds,
|
|
191
|
+
metadata,
|
|
191
192
|
}) {
|
|
192
193
|
const { data } = await apiFetch('/api/agent/messages/send', {
|
|
193
194
|
method: 'POST',
|
|
@@ -200,6 +201,7 @@ export async function sendAgentMessage({
|
|
|
200
201
|
runtime_host_id: runtimeHostId ?? null,
|
|
201
202
|
visibility: visibility || null,
|
|
202
203
|
media_asset_ids: Array.isArray(mediaAssetIds) && mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
|
|
204
|
+
metadata: metadata ?? undefined,
|
|
203
205
|
}),
|
|
204
206
|
});
|
|
205
207
|
return data || null;
|
|
@@ -484,6 +486,193 @@ export async function removeAgentGroupMember({
|
|
|
484
486
|
);
|
|
485
487
|
}
|
|
486
488
|
|
|
489
|
+
// ── Workstreams (CoS-managed groups) ──
|
|
490
|
+
|
|
491
|
+
export async function createWorkstream({
|
|
492
|
+
actingAgentId, name, description, charter, memberAgentIds,
|
|
493
|
+
}) {
|
|
494
|
+
return apiFetch('/api/agent/workstreams', {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
body: JSON.stringify({
|
|
497
|
+
acting_as_agent_id: actingAgentId,
|
|
498
|
+
name,
|
|
499
|
+
description: description ?? null,
|
|
500
|
+
charter: charter ?? null,
|
|
501
|
+
member_agent_ids: memberAgentIds || [],
|
|
502
|
+
}),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export async function deleteWorkstream({ actingAgentId, conversationId }) {
|
|
507
|
+
return apiFetch(
|
|
508
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}`,
|
|
509
|
+
{
|
|
510
|
+
method: 'DELETE',
|
|
511
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export async function listWorkstreams({ actingAgentId }) {
|
|
517
|
+
const params = new URLSearchParams();
|
|
518
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
519
|
+
const { data } = await apiFetch(`/api/agent/workstreams?${params}`);
|
|
520
|
+
return data || [];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ── Agents (CoS pre-allocation) ──
|
|
524
|
+
|
|
525
|
+
export async function createAgentSlot({
|
|
526
|
+
actingAgentId, name, runtime, description, displayName, model,
|
|
527
|
+
}) {
|
|
528
|
+
return apiFetch('/api/agent/agents', {
|
|
529
|
+
method: 'POST',
|
|
530
|
+
body: JSON.stringify({
|
|
531
|
+
acting_as_agent_id: actingAgentId,
|
|
532
|
+
name,
|
|
533
|
+
runtime,
|
|
534
|
+
description: description ?? null,
|
|
535
|
+
display_name: displayName ?? null,
|
|
536
|
+
model: model ?? null,
|
|
537
|
+
}),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export async function archiveAgentSlot({ actingAgentId, agentId }) {
|
|
542
|
+
return apiFetch(
|
|
543
|
+
`/api/agent/agents/${encodeURIComponent(agentId)}`,
|
|
544
|
+
{
|
|
545
|
+
method: 'DELETE',
|
|
546
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
547
|
+
},
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ── Services ──
|
|
552
|
+
|
|
553
|
+
export async function createService({
|
|
554
|
+
actingAgentId, name, description, contractSchema, endpointConfig,
|
|
555
|
+
}) {
|
|
556
|
+
return apiFetch('/api/agent/services', {
|
|
557
|
+
method: 'POST',
|
|
558
|
+
body: JSON.stringify({
|
|
559
|
+
acting_as_agent_id: actingAgentId,
|
|
560
|
+
name,
|
|
561
|
+
description: description ?? null,
|
|
562
|
+
contract_schema: contractSchema ?? null,
|
|
563
|
+
endpoint_config: endpointConfig,
|
|
564
|
+
}),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export async function updateService({ actingAgentId, serviceId, ...patch }) {
|
|
569
|
+
return apiFetch(
|
|
570
|
+
`/api/agent/services/${encodeURIComponent(serviceId)}`,
|
|
571
|
+
{
|
|
572
|
+
method: 'PATCH',
|
|
573
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId, ...patch }),
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export async function deleteService({ actingAgentId, serviceId }) {
|
|
579
|
+
return apiFetch(
|
|
580
|
+
`/api/agent/services/${encodeURIComponent(serviceId)}`,
|
|
581
|
+
{
|
|
582
|
+
method: 'DELETE',
|
|
583
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
584
|
+
},
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export async function listServices({ actingAgentId }) {
|
|
589
|
+
const params = new URLSearchParams();
|
|
590
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
591
|
+
const { data } = await apiFetch(`/api/agent/services?${params}`);
|
|
592
|
+
return data || [];
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export async function getServiceInfo({ actingAgentId, name }) {
|
|
596
|
+
const params = new URLSearchParams();
|
|
597
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
598
|
+
return apiFetch(
|
|
599
|
+
`/api/agent/services/${encodeURIComponent(name)}/info?${params}`,
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export async function callService({ actingAgentId, name, input }) {
|
|
604
|
+
return apiFetch(
|
|
605
|
+
`/api/agent/services/${encodeURIComponent(name)}/call`,
|
|
606
|
+
{
|
|
607
|
+
method: 'POST',
|
|
608
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId, input }),
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// ── Credentials (CoS slot creation) ──
|
|
614
|
+
|
|
615
|
+
export async function requestCredential({
|
|
616
|
+
actingAgentId, name, description, workstreamId,
|
|
617
|
+
}) {
|
|
618
|
+
return apiFetch('/api/agent/credentials', {
|
|
619
|
+
method: 'POST',
|
|
620
|
+
body: JSON.stringify({
|
|
621
|
+
acting_as_agent_id: actingAgentId,
|
|
622
|
+
name,
|
|
623
|
+
description: description ?? null,
|
|
624
|
+
workstream_id: workstreamId ?? null,
|
|
625
|
+
}),
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ── Workstream dashboard ──
|
|
630
|
+
|
|
631
|
+
export async function setWorkstreamDashboard({
|
|
632
|
+
actingAgentId, conversationId, dataJson, htmlTemplate,
|
|
633
|
+
}) {
|
|
634
|
+
const body = { acting_as_agent_id: actingAgentId };
|
|
635
|
+
// Distinguish "omit" from "set to null" — only include keys the caller
|
|
636
|
+
// explicitly passed (including null clears the field).
|
|
637
|
+
if (dataJson !== undefined) body.data_json = dataJson;
|
|
638
|
+
if (htmlTemplate !== undefined) body.html_template = htmlTemplate;
|
|
639
|
+
return apiFetch(
|
|
640
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard`,
|
|
641
|
+
{ method: 'POST', body: JSON.stringify(body) },
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export async function getWorkstreamDashboard({ actingAgentId, conversationId }) {
|
|
646
|
+
const params = new URLSearchParams();
|
|
647
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
648
|
+
return apiFetch(
|
|
649
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard?${params}`,
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ── Workstream charter ──
|
|
654
|
+
|
|
655
|
+
export async function getWorkstreamCharter({ actingAgentId, conversationId }) {
|
|
656
|
+
const params = new URLSearchParams();
|
|
657
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
658
|
+
return apiFetch(
|
|
659
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter?${params}`,
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export async function setWorkstreamCharter({ actingAgentId, conversationId, charter }) {
|
|
664
|
+
return apiFetch(
|
|
665
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter`,
|
|
666
|
+
{
|
|
667
|
+
method: 'POST',
|
|
668
|
+
body: JSON.stringify({
|
|
669
|
+
acting_as_agent_id: actingAgentId,
|
|
670
|
+
charter: charter ?? null,
|
|
671
|
+
}),
|
|
672
|
+
},
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
487
676
|
// ── Channel event pipe ──
|
|
488
677
|
|
|
489
678
|
export async function postEvent({ agent, agent_id, runtime_host_id, session_id, cwd, runtime_version, event, required = false }) {
|
|
@@ -140,14 +140,61 @@ function buildGroupContextBlock(msg) {
|
|
|
140
140
|
].join('\n');
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// Charter is the workstream's CoS-authored markdown spec. It's a stable
|
|
144
|
+
// prefix-cacheable block — same bytes across every turn in the same
|
|
145
|
+
// conversation — so it sits above the per-turn envelope.
|
|
146
|
+
function buildCharterBlock(msg) {
|
|
147
|
+
const charter = (msg.conversation_charter || '').trim();
|
|
148
|
+
if (!charter) return '';
|
|
149
|
+
return ['[charter]', charter, '[/charter]'].join('\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// CoS-only addendum. Splice above the per-turn envelope (after charter,
|
|
153
|
+
// before group context) when the recipient is the user's CoS.
|
|
154
|
+
// Same bytes every turn → still prefix-cache-friendly.
|
|
155
|
+
const COS_ADDENDUM = [
|
|
156
|
+
'[cos-role]',
|
|
157
|
+
'You are the user\'s Chief of Staff (CoS). You answer to the user. In every workstream:',
|
|
158
|
+
'',
|
|
159
|
+
'1. Push — break goals into agent-executable tasks, supervise progress, do not let work stall in place.',
|
|
160
|
+
'2. Simplify — agents should work simply and reliably. Stop over-engineering, junk-code piles, and detours.',
|
|
161
|
+
'3. Track — keep an entry per workstream in MEMORY.md; update it whenever state changes.',
|
|
162
|
+
'4. Represent — bundle resource / decision / approval requests and bring them to the user; do not flood the user with raw asks.',
|
|
163
|
+
'',
|
|
164
|
+
'Wake-on-event: every turn (including ambient delivery) you must read the message, update MEMORY.md if anything changed, and only reply when you need to intervene. Otherwise stop silently.',
|
|
165
|
+
'',
|
|
166
|
+
'Posture: bring a proposal, default to acting on it, change course if the user objects. "I propose A — reasons 1/2/3. Unless you redirect within 24h, I\'ll proceed with A." Not "should we do A or B?"',
|
|
167
|
+
'',
|
|
168
|
+
'Owner model: hold the user\'s preferences and constraints across workstreams. Apply them consistently. Do not "for-your-own-good" around stated preferences.',
|
|
169
|
+
'',
|
|
170
|
+
'奏折 / report posture: publish status to the user by sending a message into the CoS DM with `ticlawk message send --target dm:@<owner> --kind report` (stdin = the report body). Use this for scheduled summaries, escalations, or workstream check-ins. Reports surface in the Office → 奏折 sub-tab; chat replies do not. Do not over-spam reports — one per meaningful state change.',
|
|
171
|
+
'',
|
|
172
|
+
'Dashboard posture: maintain a per-workstream dashboard via `ticlawk dashboard set --target "#<ws>"` (stdin JSON: { data_json, html_template }). data_json holds the live numbers; html_template is the hand-written rendering. CoS replaces wholesale — there is no partial update protocol, and there is no schema lock-in on data_json. Use the dashboard for at-a-glance state; use 奏折 for narrative.',
|
|
173
|
+
'',
|
|
174
|
+
'Cadence: schedule your own daily 晨报 via `ticlawk reminder schedule --title "晨报" --in-minutes 1440 --anchor-conversation-id <your DM with owner>` after you handle this turn. The reminder fires by waking you with a system message; treat that wake as the trigger to compile the day\'s 奏折 across workstreams. Re-schedule on each fire so the cadence persists. Skip a day only if you are explicitly told to.',
|
|
175
|
+
'[/cos-role]',
|
|
176
|
+
].join('\n');
|
|
177
|
+
|
|
178
|
+
function buildCosAddendum(msg) {
|
|
179
|
+
if (!msg.recipient_is_cos) return '';
|
|
180
|
+
return COS_ADDENDUM;
|
|
181
|
+
}
|
|
182
|
+
|
|
143
183
|
// Wrap each per-turn message with an explicit reply instruction so the
|
|
144
184
|
// runtime LLM never has to remember the standing prompt to figure out
|
|
145
185
|
// HOW to reply. Codex in particular treats the developerInstructions as
|
|
146
186
|
// background and ignores the chat-send pattern without this per-turn
|
|
147
187
|
// nudge.
|
|
148
|
-
function buildWakePromptText({ envelopeHeader, target, rawText, groupContext }) {
|
|
188
|
+
function buildWakePromptText({ envelopeHeader, target, rawText, groupContext, charterBlock, cosAddendum }) {
|
|
149
189
|
const body = `${envelopeHeader} ${rawText || ''}`.trim();
|
|
150
|
-
const lines = [
|
|
190
|
+
const lines = [];
|
|
191
|
+
if (charterBlock) {
|
|
192
|
+
lines.push(charterBlock, '');
|
|
193
|
+
}
|
|
194
|
+
if (cosAddendum) {
|
|
195
|
+
lines.push(cosAddendum, '');
|
|
196
|
+
}
|
|
197
|
+
lines.push('New message received:', '', body);
|
|
151
198
|
if (groupContext) {
|
|
152
199
|
lines.push('', groupContext);
|
|
153
200
|
}
|
|
@@ -181,8 +228,10 @@ export function normalizeInboundMessage(msg) {
|
|
|
181
228
|
const header = baseHeader + taskSuffix + reactionsSuffix;
|
|
182
229
|
const target = buildEnvelopeTarget(enriched);
|
|
183
230
|
const groupContext = buildGroupContextBlock(enriched);
|
|
231
|
+
const charterBlock = buildCharterBlock(enriched);
|
|
232
|
+
const cosAddendum = buildCosAddendum(enriched);
|
|
184
233
|
const text = header
|
|
185
|
-
? buildWakePromptText({ envelopeHeader: header, target, rawText, groupContext })
|
|
234
|
+
? buildWakePromptText({ envelopeHeader: header, target, rawText, groupContext, charterBlock, cosAddendum })
|
|
186
235
|
: rawText;
|
|
187
236
|
return {
|
|
188
237
|
bindingId: recipientAgentId,
|
|
@@ -1013,7 +1062,17 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1013
1062
|
|
|
1014
1063
|
function connectWakeSocket() {
|
|
1015
1064
|
connectorSocket = new TiclawkWakeClient({
|
|
1016
|
-
|
|
1065
|
+
// host_id rides on the WS query string so connector-wake can flip
|
|
1066
|
+
// runtime_hosts.online for the right row. Empty host_id is
|
|
1067
|
+
// tolerated by the server (it just skips the state write).
|
|
1068
|
+
getUrl: () => {
|
|
1069
|
+
const base = String(api.getConnectorWsUrl() || '').trim();
|
|
1070
|
+
if (!base) return '';
|
|
1071
|
+
const hostId = String(getHostId() || '').trim();
|
|
1072
|
+
if (!hostId) return base;
|
|
1073
|
+
const sep = base.includes('?') ? '&' : '?';
|
|
1074
|
+
return `${base}${sep}host_id=${encodeURIComponent(hostId)}`;
|
|
1075
|
+
},
|
|
1017
1076
|
getApiKey: api.getApiKey,
|
|
1018
1077
|
onEvent: handleWakeEvent,
|
|
1019
1078
|
onStatus: handleWakeStatus,
|