ticlawk 0.1.16-dev.11 → 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/http.mjs +102 -0
- package/src/runtimes/_shared/standing-prompt.mjs +2 -0
|
@@ -158,6 +158,7 @@ export async function handleMessageSend(req, body, ctx) {
|
|
|
158
158
|
replyToMessageId: body?.reply_to_message_id || threadRootMsgId || null,
|
|
159
159
|
runtimeHostId: getRuntimeHostId(req, body),
|
|
160
160
|
mediaAssetIds,
|
|
161
|
+
metadata: body?.metadata,
|
|
161
162
|
});
|
|
162
163
|
debugLog('agent-cli', 'send.ok', {
|
|
163
164
|
actingAgentId,
|
|
@@ -716,6 +717,305 @@ export async function handleGroupMembersRemove(req, body, ctx) {
|
|
|
716
717
|
}
|
|
717
718
|
}
|
|
718
719
|
|
|
720
|
+
export async function handleWorkstreamCreate(req, body, ctx) {
|
|
721
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
722
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
723
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
724
|
+
const name = String(body?.name || '').trim();
|
|
725
|
+
if (!name) return { status: 400, body: { error: 'name is required' } };
|
|
726
|
+
try {
|
|
727
|
+
const data = await api.createWorkstream({
|
|
728
|
+
actingAgentId,
|
|
729
|
+
name,
|
|
730
|
+
description: body?.description || null,
|
|
731
|
+
charter: body?.charter ?? null,
|
|
732
|
+
memberAgentIds: Array.isArray(body?.member_agent_ids) ? body.member_agent_ids : [],
|
|
733
|
+
});
|
|
734
|
+
invalidateServerInfoCache(actingAgentId);
|
|
735
|
+
debugLog('agent-cli', 'workstream.create', {
|
|
736
|
+
actingAgentId, conversationId: data?.conversation?.id,
|
|
737
|
+
});
|
|
738
|
+
return { status: 200, body: data };
|
|
739
|
+
} catch (err) {
|
|
740
|
+
return { status: err?.status || 500, body: { error: err?.message || 'workstream create failed' } };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export async function handleWorkstreamDelete(req, body, ctx) {
|
|
745
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
746
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
747
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
748
|
+
let conversationId = body?.conversation_id || null;
|
|
749
|
+
if (!conversationId && body?.target) {
|
|
750
|
+
const resolved = await resolveTarget(actingAgentId, String(body.target));
|
|
751
|
+
if (resolved.error) return { status: 404, body: { error: resolved.error } };
|
|
752
|
+
conversationId = resolved.conversationId;
|
|
753
|
+
}
|
|
754
|
+
if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
|
|
755
|
+
try {
|
|
756
|
+
const data = await api.deleteWorkstream({ actingAgentId, conversationId });
|
|
757
|
+
invalidateServerInfoCache(actingAgentId);
|
|
758
|
+
return { status: 200, body: data };
|
|
759
|
+
} catch (err) {
|
|
760
|
+
return { status: err?.status || 500, body: { error: err?.message || 'workstream delete failed' } };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export async function handleWorkstreamList(req, query, ctx) {
|
|
765
|
+
const actingAgentId = getActingAgentId(req, query);
|
|
766
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
767
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
768
|
+
try {
|
|
769
|
+
const data = await api.listWorkstreams({ actingAgentId });
|
|
770
|
+
return { status: 200, body: { data } };
|
|
771
|
+
} catch (err) {
|
|
772
|
+
return { status: err?.status || 500, body: { error: err?.message || 'workstream list failed' } };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export async function handleAgentCreate(req, body, ctx) {
|
|
777
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
778
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
779
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
780
|
+
const name = String(body?.name || '').trim();
|
|
781
|
+
const runtime = String(body?.runtime || '').trim();
|
|
782
|
+
if (!name) return { status: 400, body: { error: 'name is required' } };
|
|
783
|
+
if (!runtime) return { status: 400, body: { error: 'runtime is required' } };
|
|
784
|
+
try {
|
|
785
|
+
const data = await api.createAgentSlot({
|
|
786
|
+
actingAgentId,
|
|
787
|
+
name,
|
|
788
|
+
runtime,
|
|
789
|
+
description: body?.description || null,
|
|
790
|
+
displayName: body?.display_name || null,
|
|
791
|
+
model: body?.model || null,
|
|
792
|
+
});
|
|
793
|
+
return { status: 200, body: data };
|
|
794
|
+
} catch (err) {
|
|
795
|
+
return { status: err?.status || 500, body: { error: err?.message || 'agent create failed' } };
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export async function handleAgentDelete(req, body, ctx) {
|
|
800
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
801
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
802
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
803
|
+
const agentId = String(body?.agent_id || '').trim();
|
|
804
|
+
if (!agentId) return { status: 400, body: { error: 'agent_id is required' } };
|
|
805
|
+
try {
|
|
806
|
+
const data = await api.archiveAgentSlot({ actingAgentId, agentId });
|
|
807
|
+
return { status: 200, body: data };
|
|
808
|
+
} catch (err) {
|
|
809
|
+
return { status: err?.status || 500, body: { error: err?.message || 'agent delete failed' } };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export async function handleServiceCreate(req, body, ctx) {
|
|
814
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
815
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
816
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
817
|
+
const name = String(body?.name || '').trim();
|
|
818
|
+
if (!name) return { status: 400, body: { error: 'name is required' } };
|
|
819
|
+
if (!body?.endpoint_config || typeof body.endpoint_config !== 'object') {
|
|
820
|
+
return { status: 400, body: { error: 'endpoint_config is required' } };
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
const data = await api.createService({
|
|
824
|
+
actingAgentId, name,
|
|
825
|
+
description: body?.description || null,
|
|
826
|
+
contractSchema: body?.contract_schema ?? null,
|
|
827
|
+
endpointConfig: body.endpoint_config,
|
|
828
|
+
});
|
|
829
|
+
return { status: 200, body: data };
|
|
830
|
+
} catch (err) {
|
|
831
|
+
return { status: err?.status || 500, body: { error: err?.message || 'service create failed' } };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export async function handleServiceUpdate(req, body, ctx) {
|
|
836
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
837
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
838
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
839
|
+
const serviceId = String(body?.service_id || '').trim();
|
|
840
|
+
if (!serviceId) return { status: 400, body: { error: 'service_id is required' } };
|
|
841
|
+
const patch = {};
|
|
842
|
+
if ('description' in (body || {})) patch.description = body.description;
|
|
843
|
+
if ('contract_schema' in (body || {})) patch.contract_schema = body.contract_schema;
|
|
844
|
+
if ('endpoint_config' in (body || {})) patch.endpoint_config = body.endpoint_config;
|
|
845
|
+
if ('status' in (body || {})) patch.status = body.status;
|
|
846
|
+
try {
|
|
847
|
+
const data = await api.updateService({ actingAgentId, serviceId, ...patch });
|
|
848
|
+
return { status: 200, body: data };
|
|
849
|
+
} catch (err) {
|
|
850
|
+
return { status: err?.status || 500, body: { error: err?.message || 'service update failed' } };
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export async function handleServiceDelete(req, body, ctx) {
|
|
855
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
856
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
857
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
858
|
+
const serviceId = String(body?.service_id || '').trim();
|
|
859
|
+
if (!serviceId) return { status: 400, body: { error: 'service_id is required' } };
|
|
860
|
+
try {
|
|
861
|
+
const data = await api.deleteService({ actingAgentId, serviceId });
|
|
862
|
+
return { status: 200, body: data };
|
|
863
|
+
} catch (err) {
|
|
864
|
+
return { status: err?.status || 500, body: { error: err?.message || 'service delete failed' } };
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export async function handleServiceList(req, query, ctx) {
|
|
869
|
+
const actingAgentId = getActingAgentId(req, query);
|
|
870
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
871
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
872
|
+
try {
|
|
873
|
+
const data = await api.listServices({ actingAgentId });
|
|
874
|
+
return { status: 200, body: { data } };
|
|
875
|
+
} catch (err) {
|
|
876
|
+
return { status: err?.status || 500, body: { error: err?.message || 'service list failed' } };
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
export async function handleServiceInfo(req, query, ctx) {
|
|
881
|
+
const actingAgentId = getActingAgentId(req, query);
|
|
882
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
883
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
884
|
+
const name = String(query?.name || '').trim();
|
|
885
|
+
if (!name) return { status: 400, body: { error: 'name is required' } };
|
|
886
|
+
try {
|
|
887
|
+
const data = await api.getServiceInfo({ actingAgentId, name });
|
|
888
|
+
return { status: 200, body: data };
|
|
889
|
+
} catch (err) {
|
|
890
|
+
return { status: err?.status || 500, body: { error: err?.message || 'service info failed' } };
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
export async function handleServiceCall(req, body, ctx) {
|
|
895
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
896
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
897
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
898
|
+
const name = String(body?.name || '').trim();
|
|
899
|
+
if (!name) return { status: 400, body: { error: 'name is required' } };
|
|
900
|
+
try {
|
|
901
|
+
const data = await api.callService({
|
|
902
|
+
actingAgentId, name,
|
|
903
|
+
input: body?.input ?? null,
|
|
904
|
+
});
|
|
905
|
+
return { status: 200, body: data };
|
|
906
|
+
} catch (err) {
|
|
907
|
+
return { status: err?.status || 500, body: { error: err?.message || 'service call failed' } };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
export async function handleCredentialRequest(req, body, ctx) {
|
|
912
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
913
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
914
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
915
|
+
const name = String(body?.name || '').trim();
|
|
916
|
+
if (!name) return { status: 400, body: { error: 'name is required' } };
|
|
917
|
+
let workstreamId = body?.workstream_id || null;
|
|
918
|
+
if (!workstreamId && body?.target) {
|
|
919
|
+
const resolved = await resolveTarget(actingAgentId, String(body.target));
|
|
920
|
+
if (resolved.error) return { status: 404, body: { error: resolved.error } };
|
|
921
|
+
workstreamId = resolved.conversationId;
|
|
922
|
+
}
|
|
923
|
+
try {
|
|
924
|
+
const data = await api.requestCredential({
|
|
925
|
+
actingAgentId,
|
|
926
|
+
name,
|
|
927
|
+
description: body?.description || null,
|
|
928
|
+
workstreamId,
|
|
929
|
+
});
|
|
930
|
+
return { status: 200, body: data };
|
|
931
|
+
} catch (err) {
|
|
932
|
+
return { status: err?.status || 500, body: { error: err?.message || 'credential request failed' } };
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export async function handleWorkstreamDashboardSet(req, body, ctx) {
|
|
937
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
938
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
939
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
940
|
+
let conversationId = body?.conversation_id || null;
|
|
941
|
+
if (!conversationId && body?.target) {
|
|
942
|
+
const resolved = await resolveTarget(actingAgentId, String(body.target));
|
|
943
|
+
if (resolved.error) return { status: 404, body: { error: resolved.error } };
|
|
944
|
+
conversationId = resolved.conversationId;
|
|
945
|
+
}
|
|
946
|
+
if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
|
|
947
|
+
const payload = { actingAgentId, conversationId };
|
|
948
|
+
if ('data_json' in (body || {})) payload.dataJson = body.data_json;
|
|
949
|
+
if ('html_template' in (body || {})) payload.htmlTemplate = body.html_template;
|
|
950
|
+
try {
|
|
951
|
+
const data = await api.setWorkstreamDashboard(payload);
|
|
952
|
+
return { status: 200, body: data };
|
|
953
|
+
} catch (err) {
|
|
954
|
+
return { status: err?.status || 500, body: { error: err?.message || 'dashboard set failed' } };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
export async function handleWorkstreamDashboardGet(req, query, ctx) {
|
|
959
|
+
const actingAgentId = getActingAgentId(req, query);
|
|
960
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
961
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
962
|
+
let conversationId = query?.conversation_id || null;
|
|
963
|
+
if (!conversationId && query?.target) {
|
|
964
|
+
const resolved = await resolveTarget(actingAgentId, String(query.target));
|
|
965
|
+
if (resolved.error) return { status: 404, body: { error: resolved.error } };
|
|
966
|
+
conversationId = resolved.conversationId;
|
|
967
|
+
}
|
|
968
|
+
if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
|
|
969
|
+
try {
|
|
970
|
+
const data = await api.getWorkstreamDashboard({ actingAgentId, conversationId });
|
|
971
|
+
return { status: 200, body: data };
|
|
972
|
+
} catch (err) {
|
|
973
|
+
return { status: err?.status || 500, body: { error: err?.message || 'dashboard get failed' } };
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
export async function handleWorkstreamCharterGet(req, query, ctx) {
|
|
978
|
+
const actingAgentId = getActingAgentId(req, query);
|
|
979
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
980
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
981
|
+
let conversationId = query?.conversation_id || null;
|
|
982
|
+
if (!conversationId && query?.target) {
|
|
983
|
+
const resolved = await resolveTarget(actingAgentId, String(query.target));
|
|
984
|
+
if (resolved.error) return { status: 404, body: { error: resolved.error } };
|
|
985
|
+
conversationId = resolved.conversationId;
|
|
986
|
+
}
|
|
987
|
+
if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
|
|
988
|
+
try {
|
|
989
|
+
const data = await api.getWorkstreamCharter({ actingAgentId, conversationId });
|
|
990
|
+
return { status: 200, body: data };
|
|
991
|
+
} catch (err) {
|
|
992
|
+
return { status: err?.status || 500, body: { error: err?.message || 'charter get failed' } };
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export async function handleWorkstreamCharterSet(req, body, ctx) {
|
|
997
|
+
const actingAgentId = getActingAgentId(req, body);
|
|
998
|
+
const v = validateActingAgent(actingAgentId, ctx);
|
|
999
|
+
if (!v.ok) return { status: v.status, body: { error: v.error } };
|
|
1000
|
+
let conversationId = body?.conversation_id || null;
|
|
1001
|
+
if (!conversationId && body?.target) {
|
|
1002
|
+
const resolved = await resolveTarget(actingAgentId, String(body.target));
|
|
1003
|
+
if (resolved.error) return { status: 404, body: { error: resolved.error } };
|
|
1004
|
+
conversationId = resolved.conversationId;
|
|
1005
|
+
}
|
|
1006
|
+
if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
|
|
1007
|
+
const charter = typeof body?.charter === 'string' ? body.charter : null;
|
|
1008
|
+
try {
|
|
1009
|
+
const data = await api.setWorkstreamCharter({ actingAgentId, conversationId, charter });
|
|
1010
|
+
debugLog('agent-cli', 'workstream.charter.set', {
|
|
1011
|
+
actingAgentId, conversationId, len: charter?.length ?? 0,
|
|
1012
|
+
});
|
|
1013
|
+
return { status: 200, body: data };
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
return { status: err?.status || 500, body: { error: err?.message || 'charter set failed' } };
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
719
1019
|
export async function handleServerInfo(req, query, ctx) {
|
|
720
1020
|
const actingAgentId = getActingAgentId(req, query);
|
|
721
1021
|
const v = validateActingAgent(actingAgentId, ctx);
|
package/src/core/agent-home.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* lives in cwd, agent reads it via `cat MEMORY.md`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { AF_HOME } from './config.mjs';
|
|
13
13
|
|
|
@@ -37,9 +37,53 @@ export function ensureAgentHome(agentId, { displayName } = {}) {
|
|
|
37
37
|
if (!existsSync(memoryPath)) {
|
|
38
38
|
writeFileSync(memoryPath, buildInitialMemoryMd({ displayName, home }), 'utf8');
|
|
39
39
|
}
|
|
40
|
+
ensureSkillSymlinks(home);
|
|
40
41
|
return home;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Cross-runtime skill discovery: every runtime (Claude Code, Codex,
|
|
46
|
+
* opencode, pi, openclaw) auto-discovers SKILL.md under at least one of
|
|
47
|
+
* .claude/skills/, .codex/skills/, .opencode/skills/, .pi/skills/, or
|
|
48
|
+
* .agents/skills/. We pin everything to a single source-of-truth
|
|
49
|
+
* directory (.agents/skills/) and symlink the rest so a skill written
|
|
50
|
+
* once is visible to every runtime without sync machinery.
|
|
51
|
+
*
|
|
52
|
+
* .pi and .opencode skip the symlink — both natively scan
|
|
53
|
+
* .agents/skills/ in addition to their own folder.
|
|
54
|
+
*
|
|
55
|
+
* No migration / no recovery: if the target path already exists as a
|
|
56
|
+
* real dir or wrong-target symlink, we leave it alone. The user (or a
|
|
57
|
+
* future agent) resolves it manually. See cos_impl.md §一.不为错误兜底.
|
|
58
|
+
*/
|
|
59
|
+
function ensureSkillSymlinks(home) {
|
|
60
|
+
const realRoot = join(home, '.agents', 'skills');
|
|
61
|
+
mkdirSync(realRoot, { recursive: true });
|
|
62
|
+
|
|
63
|
+
const links = [
|
|
64
|
+
{ dir: join(home, '.claude'), name: 'skills', target: '../.agents/skills' },
|
|
65
|
+
{ dir: join(home, '.codex'), name: 'skills', target: '../.agents/skills' },
|
|
66
|
+
// openclaw expects skills/ at the home root
|
|
67
|
+
{ dir: home, name: 'skills', target: '.agents/skills' },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const { dir, name, target } of links) {
|
|
71
|
+
mkdirSync(dir, { recursive: true });
|
|
72
|
+
const linkPath = join(dir, name);
|
|
73
|
+
let stat = null;
|
|
74
|
+
try { stat = lstatSync(linkPath); } catch { /* not present */ }
|
|
75
|
+
if (stat) continue; // path already exists — do not touch
|
|
76
|
+
try {
|
|
77
|
+
symlinkSync(target, linkPath, 'dir');
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Best-effort: an EEXIST race or platform that disallows symlinks
|
|
80
|
+
// gets logged but doesn't fail spawn. Skills just won't be visible
|
|
81
|
+
// for that runtime via this folder.
|
|
82
|
+
console.warn(`[agent-home] failed to symlink ${linkPath} -> ${target}: ${err?.message || err}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
43
87
|
function buildInitialMemoryMd({ displayName, home }) {
|
|
44
88
|
const lines = [
|
|
45
89
|
`# ${displayName || 'Agent'}`,
|
package/src/core/http.mjs
CHANGED
|
@@ -3,6 +3,22 @@ import {
|
|
|
3
3
|
handleAttachmentUpload,
|
|
4
4
|
handleAttachmentView,
|
|
5
5
|
handleGroupCreate,
|
|
6
|
+
handleWorkstreamCharterGet,
|
|
7
|
+
handleWorkstreamCharterSet,
|
|
8
|
+
handleWorkstreamCreate,
|
|
9
|
+
handleWorkstreamDelete,
|
|
10
|
+
handleWorkstreamList,
|
|
11
|
+
handleAgentCreate,
|
|
12
|
+
handleAgentDelete,
|
|
13
|
+
handleWorkstreamDashboardSet,
|
|
14
|
+
handleWorkstreamDashboardGet,
|
|
15
|
+
handleCredentialRequest,
|
|
16
|
+
handleServiceCreate,
|
|
17
|
+
handleServiceUpdate,
|
|
18
|
+
handleServiceDelete,
|
|
19
|
+
handleServiceList,
|
|
20
|
+
handleServiceInfo,
|
|
21
|
+
handleServiceCall,
|
|
6
22
|
handleGroupMembers,
|
|
7
23
|
handleGroupMembersAdd,
|
|
8
24
|
handleGroupMembersRemove,
|
|
@@ -223,6 +239,92 @@ export function startLocalHttpServer({ port, adapter, ctx }) {
|
|
|
223
239
|
const r = await handleServerInfo(req, parseQuery(req.url || ''), cliCtx);
|
|
224
240
|
return writeJson(res, r.status, r.body);
|
|
225
241
|
}
|
|
242
|
+
if (urlNoQuery === '/agent/workstream/charter/get' && method === 'GET') {
|
|
243
|
+
const r = await handleWorkstreamCharterGet(req, parseQuery(req.url || ''), cliCtx);
|
|
244
|
+
return writeJson(res, r.status, r.body);
|
|
245
|
+
}
|
|
246
|
+
if (urlNoQuery === '/agent/workstream/charter/set' && method === 'POST') {
|
|
247
|
+
const body = await readJsonBody(req);
|
|
248
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
249
|
+
const r = await handleWorkstreamCharterSet(req, body, cliCtx);
|
|
250
|
+
return writeJson(res, r.status, r.body);
|
|
251
|
+
}
|
|
252
|
+
if (urlNoQuery === '/agent/workstream/create' && method === 'POST') {
|
|
253
|
+
const body = await readJsonBody(req);
|
|
254
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
255
|
+
const r = await handleWorkstreamCreate(req, body, cliCtx);
|
|
256
|
+
return writeJson(res, r.status, r.body);
|
|
257
|
+
}
|
|
258
|
+
if (urlNoQuery === '/agent/workstream/delete' && method === 'POST') {
|
|
259
|
+
const body = await readJsonBody(req);
|
|
260
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
261
|
+
const r = await handleWorkstreamDelete(req, body, cliCtx);
|
|
262
|
+
return writeJson(res, r.status, r.body);
|
|
263
|
+
}
|
|
264
|
+
if (urlNoQuery === '/agent/workstream/list' && method === 'GET') {
|
|
265
|
+
const r = await handleWorkstreamList(req, parseQuery(req.url || ''), cliCtx);
|
|
266
|
+
return writeJson(res, r.status, r.body);
|
|
267
|
+
}
|
|
268
|
+
if (urlNoQuery === '/agent/agent/create' && method === 'POST') {
|
|
269
|
+
const body = await readJsonBody(req);
|
|
270
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
271
|
+
const r = await handleAgentCreate(req, body, cliCtx);
|
|
272
|
+
return writeJson(res, r.status, r.body);
|
|
273
|
+
}
|
|
274
|
+
if (urlNoQuery === '/agent/agent/delete' && method === 'POST') {
|
|
275
|
+
const body = await readJsonBody(req);
|
|
276
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
277
|
+
const r = await handleAgentDelete(req, body, cliCtx);
|
|
278
|
+
return writeJson(res, r.status, r.body);
|
|
279
|
+
}
|
|
280
|
+
if (urlNoQuery === '/agent/dashboard/set' && method === 'POST') {
|
|
281
|
+
const body = await readJsonBody(req);
|
|
282
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
283
|
+
const r = await handleWorkstreamDashboardSet(req, body, cliCtx);
|
|
284
|
+
return writeJson(res, r.status, r.body);
|
|
285
|
+
}
|
|
286
|
+
if (urlNoQuery === '/agent/dashboard/get' && method === 'GET') {
|
|
287
|
+
const r = await handleWorkstreamDashboardGet(req, parseQuery(req.url || ''), cliCtx);
|
|
288
|
+
return writeJson(res, r.status, r.body);
|
|
289
|
+
}
|
|
290
|
+
if (urlNoQuery === '/agent/credential/request' && method === 'POST') {
|
|
291
|
+
const body = await readJsonBody(req);
|
|
292
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
293
|
+
const r = await handleCredentialRequest(req, body, cliCtx);
|
|
294
|
+
return writeJson(res, r.status, r.body);
|
|
295
|
+
}
|
|
296
|
+
if (urlNoQuery === '/agent/service/create' && method === 'POST') {
|
|
297
|
+
const body = await readJsonBody(req);
|
|
298
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
299
|
+
const r = await handleServiceCreate(req, body, cliCtx);
|
|
300
|
+
return writeJson(res, r.status, r.body);
|
|
301
|
+
}
|
|
302
|
+
if (urlNoQuery === '/agent/service/update' && method === 'POST') {
|
|
303
|
+
const body = await readJsonBody(req);
|
|
304
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
305
|
+
const r = await handleServiceUpdate(req, body, cliCtx);
|
|
306
|
+
return writeJson(res, r.status, r.body);
|
|
307
|
+
}
|
|
308
|
+
if (urlNoQuery === '/agent/service/delete' && method === 'POST') {
|
|
309
|
+
const body = await readJsonBody(req);
|
|
310
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
311
|
+
const r = await handleServiceDelete(req, body, cliCtx);
|
|
312
|
+
return writeJson(res, r.status, r.body);
|
|
313
|
+
}
|
|
314
|
+
if (urlNoQuery === '/agent/service/list' && method === 'GET') {
|
|
315
|
+
const r = await handleServiceList(req, parseQuery(req.url || ''), cliCtx);
|
|
316
|
+
return writeJson(res, r.status, r.body);
|
|
317
|
+
}
|
|
318
|
+
if (urlNoQuery === '/agent/service/info' && method === 'GET') {
|
|
319
|
+
const r = await handleServiceInfo(req, parseQuery(req.url || ''), cliCtx);
|
|
320
|
+
return writeJson(res, r.status, r.body);
|
|
321
|
+
}
|
|
322
|
+
if (urlNoQuery === '/agent/service/call' && method === 'POST') {
|
|
323
|
+
const body = await readJsonBody(req);
|
|
324
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
325
|
+
const r = await handleServiceCall(req, body, cliCtx);
|
|
326
|
+
return writeJson(res, r.status, r.body);
|
|
327
|
+
}
|
|
226
328
|
|
|
227
329
|
writeJson(res, 404, { error: 'not found' });
|
|
228
330
|
} catch (err) {
|
|
@@ -21,6 +21,8 @@ to users or groups.
|
|
|
21
21
|
- Always communicate through the \`ticlawk\` CLI. This is your only output channel.
|
|
22
22
|
- Always claim a task via \`ticlawk task claim\` before doing any substantive work on it. If the claim fails, stop immediately and pick a different task.
|
|
23
23
|
- Use only the provided \`ticlawk\` CLI commands for messaging.
|
|
24
|
+
- If the turn opens with a \`[charter] ... [/charter]\` block, that is the workstream's binding spec (规章制度). Every action you take in this conversation must conform to it. If a request conflicts with the charter, stop and surface the conflict to the Chief of Staff (CoS) — don't silently route around it.
|
|
25
|
+
- Shared services are CoS-published tools you can invoke. \`ticlawk service list\` shows available names + descriptions; \`ticlawk service info --name X\` shows the input contract; \`ticlawk service call --name X\` runs it with stdin JSON input. There is no retry — call failure means stop and report; do not loop.
|
|
24
26
|
|
|
25
27
|
## Startup checklist (every turn)
|
|
26
28
|
|