@pi-in-go/pigpen-acp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CREDITS.md +23 -0
  2. package/LICENSE +22 -0
  3. package/README.md +110 -0
  4. package/extensions/acp/cmd/pig-acp/e2e_llm_test.go +168 -0
  5. package/extensions/acp/cmd/pig-acp/e2e_test.go +541 -0
  6. package/extensions/acp/cmd/pig-acp/go.mod +6 -0
  7. package/extensions/acp/cmd/pig-acp/internal/acp/agent.go +784 -0
  8. package/extensions/acp/cmd/pig-acp/internal/acp/agent_builtin_test.go +536 -0
  9. package/extensions/acp/cmd/pig-acp/internal/acp/agent_commands_test.go +199 -0
  10. package/extensions/acp/cmd/pig-acp/internal/acp/agent_config_test.go +530 -0
  11. package/extensions/acp/cmd/pig-acp/internal/acp/agent_helpers_test.go +215 -0
  12. package/extensions/acp/cmd/pig-acp/internal/acp/agent_load_test.go +333 -0
  13. package/extensions/acp/cmd/pig-acp/internal/acp/agent_prompt.go +289 -0
  14. package/extensions/acp/cmd/pig-acp/internal/acp/agent_session_test.go +429 -0
  15. package/extensions/acp/cmd/pig-acp/internal/acp/auth.go +61 -0
  16. package/extensions/acp/cmd/pig-acp/internal/acp/bench_test.go +47 -0
  17. package/extensions/acp/cmd/pig-acp/internal/acp/helpers_test.go +421 -0
  18. package/extensions/acp/cmd/pig-acp/internal/acp/main_test.go +28 -0
  19. package/extensions/acp/cmd/pig-acp/internal/acp/mutation_extra_test.go +142 -0
  20. package/extensions/acp/cmd/pig-acp/internal/acp/paths.go +77 -0
  21. package/extensions/acp/cmd/pig-acp/internal/acp/pisessions.go +317 -0
  22. package/extensions/acp/cmd/pig-acp/internal/acp/protocol.go +188 -0
  23. package/extensions/acp/cmd/pig-acp/internal/acp/server.go +218 -0
  24. package/extensions/acp/cmd/pig-acp/internal/acp/server_test.go +386 -0
  25. package/extensions/acp/cmd/pig-acp/internal/acp/session.go +1068 -0
  26. package/extensions/acp/cmd/pig-acp/internal/acp/session_diff_test.go +183 -0
  27. package/extensions/acp/cmd/pig-acp/internal/acp/session_events_test.go +443 -0
  28. package/extensions/acp/cmd/pig-acp/internal/acp/session_extra_test.go +547 -0
  29. package/extensions/acp/cmd/pig-acp/internal/acp/session_usage_test.go +198 -0
  30. package/extensions/acp/cmd/pig-acp/internal/acp/settings.go +79 -0
  31. package/extensions/acp/cmd/pig-acp/internal/acp/slash.go +278 -0
  32. package/extensions/acp/cmd/pig-acp/internal/acp/slash_test.go +128 -0
  33. package/extensions/acp/cmd/pig-acp/internal/acp/startup.go +156 -0
  34. package/extensions/acp/cmd/pig-acp/internal/acp/store.go +118 -0
  35. package/extensions/acp/cmd/pig-acp/internal/acp/store_paths_test.go +466 -0
  36. package/extensions/acp/cmd/pig-acp/internal/acp/translate.go +352 -0
  37. package/extensions/acp/cmd/pig-acp/internal/acp/translate_test.go +105 -0
  38. package/extensions/acp/cmd/pig-acp/internal/acp/twin_test.go +28 -0
  39. package/extensions/acp/cmd/pig-acp/internal/acp/types.go +202 -0
  40. package/extensions/acp/cmd/pig-acp/internal/jsonrpc/bench_test.go +24 -0
  41. package/extensions/acp/cmd/pig-acp/internal/jsonrpc/jsonrpc.go +302 -0
  42. package/extensions/acp/cmd/pig-acp/internal/jsonrpc/jsonrpc_test.go +320 -0
  43. package/extensions/acp/cmd/pig-acp/internal/jsonrpc/twin_test.go +28 -0
  44. package/extensions/acp/cmd/pig-acp/internal/pirpc/kill_unix.go +19 -0
  45. package/extensions/acp/cmd/pig-acp/internal/pirpc/kill_windows.go +11 -0
  46. package/extensions/acp/cmd/pig-acp/internal/pirpc/process.go +530 -0
  47. package/extensions/acp/cmd/pig-acp/internal/pirpc/process_test.go +355 -0
  48. package/extensions/acp/cmd/pig-acp/internal/pirpc/twin_test.go +28 -0
  49. package/extensions/acp/cmd/pig-acp/main.go +177 -0
  50. package/extensions/acp/cmd/pig-acp/main_test.go +213 -0
  51. package/extensions/acp/cmd/pig-acp/sigpipe_unix.go +10 -0
  52. package/extensions/acp/cmd/pig-acp/sigpipe_windows.go +5 -0
  53. package/extensions/acp/cmd/pig-acp/testdata/ask/extension.go +28 -0
  54. package/extensions/acp/cmd/pig-acp/testdata/ask/go.mod +7 -0
  55. package/extensions/acp/cmd/pig-acp/testdata/ask/go.sum +2 -0
  56. package/extensions/acp/cmd/pig-acp/testdata/gate/extension.go +37 -0
  57. package/extensions/acp/cmd/pig-acp/testdata/gate/go.mod +7 -0
  58. package/extensions/acp/cmd/pig-acp/testdata/gate/go.sum +2 -0
  59. package/extensions/acp/cmd/pig-acp/twin_test.go +28 -0
  60. package/extensions/acp/extension.go +63 -0
  61. package/extensions/acp/extension_test.go +111 -0
  62. package/extensions/acp/fakehost_test.go +548 -0
  63. package/extensions/acp/go.mod +6 -0
  64. package/extensions/acp/go.sum +2 -0
  65. package/package.json +41 -0
  66. package/port/PORT.md +147 -0
  67. package/port/interop/package-lock.json +89 -0
  68. package/port/interop/package.json +13 -0
  69. package/port/interop/run.mjs +229 -0
  70. package/port/mutate.mjs +110 -0
  71. package/port/mutation-results.txt +107 -0
  72. package/port/mutations.json +748 -0
  73. package/port/oracle/pi-acp/LICENSE +21 -0
  74. package/port/oracle/pi-acp/README.md +209 -0
  75. package/port/oracle/pi-acp/package.json +65 -0
  76. package/port/oracle/pi-acp/src/acp/agent.ts +1661 -0
  77. package/port/oracle/pi-acp/src/acp/auth-required.ts +37 -0
  78. package/port/oracle/pi-acp/src/acp/auth.ts +59 -0
  79. package/port/oracle/pi-acp/src/acp/paths.ts +15 -0
  80. package/port/oracle/pi-acp/src/acp/pi-commands.ts +59 -0
  81. package/port/oracle/pi-acp/src/acp/pi-sessions.ts +333 -0
  82. package/port/oracle/pi-acp/src/acp/pi-settings.ts +75 -0
  83. package/port/oracle/pi-acp/src/acp/session-store.ts +68 -0
  84. package/port/oracle/pi-acp/src/acp/session.ts +1078 -0
  85. package/port/oracle/pi-acp/src/acp/slash-commands.ts +197 -0
  86. package/port/oracle/pi-acp/src/acp/translate/bash.ts +100 -0
  87. package/port/oracle/pi-acp/src/acp/translate/pi-messages.ts +17 -0
  88. package/port/oracle/pi-acp/src/acp/translate/pi-tools.ts +51 -0
  89. package/port/oracle/pi-acp/src/acp/translate/prompt.ts +71 -0
  90. package/port/oracle/pi-acp/src/index.ts +82 -0
  91. package/port/oracle/pi-acp/src/pi-rpc/command.ts +16 -0
  92. package/port/oracle/pi-acp/src/pi-rpc/process.ts +432 -0
  93. package/port/oracle/pi-acp/test/component/agent-steering-followup-modes.test.ts +143 -0
  94. package/port/oracle/pi-acp/test/component/session-diff.test.ts +214 -0
  95. package/port/oracle/pi-acp/test/component/session-events.test.ts +1102 -0
  96. package/port/oracle/pi-acp/test/component/session-list-and-load.test.ts +129 -0
  97. package/port/oracle/pi-acp/test/component/session-list-custom-session-dir.test.ts +52 -0
  98. package/port/oracle/pi-acp/test/component/session-list-scoped.test.ts +76 -0
  99. package/port/oracle/pi-acp/test/component/session-load-toolresult.test.ts +68 -0
  100. package/port/oracle/pi-acp/test/component/session-queue-cancel.test.ts +43 -0
  101. package/port/oracle/pi-acp/test/component/session-slash-commands.test.ts +37 -0
  102. package/port/oracle/pi-acp/test/component/session-thinking-modes.test.ts +22 -0
  103. package/port/oracle/pi-acp/test/component/session-title-long-session.test.ts +56 -0
  104. package/port/oracle/pi-acp/test/component/session-updatedAt-message-only.test.ts +57 -0
  105. package/port/oracle/pi-acp/test/helpers/fakes.ts +87 -0
  106. package/port/oracle/pi-acp/test/unit/auth-methods-terminal-auth-meta.test.ts +22 -0
  107. package/port/oracle/pi-acp/test/unit/builtin-commands.test.ts +60 -0
  108. package/port/oracle/pi-acp/test/unit/context-usage.test.ts +204 -0
  109. package/port/oracle/pi-acp/test/unit/merge-commands.test.ts +19 -0
  110. package/port/oracle/pi-acp/test/unit/model-thinking-levels.test.ts +268 -0
  111. package/port/oracle/pi-acp/test/unit/new-session-auth-required-when-no-models.test.ts +51 -0
  112. package/port/oracle/pi-acp/test/unit/new-session-pi-not-found.test.ts +26 -0
  113. package/port/oracle/pi-acp/test/unit/new-session-runtime-startup-errors.test.ts +148 -0
  114. package/port/oracle/pi-acp/test/unit/pi-command.test.ts +44 -0
  115. package/port/oracle/pi-acp/test/unit/pi-commands.test.ts +32 -0
  116. package/port/oracle/pi-acp/test/unit/pi-enable-embed-context-flag.test.ts +35 -0
  117. package/port/oracle/pi-acp/test/unit/pi-messages.test.ts +29 -0
  118. package/port/oracle/pi-acp/test/unit/pi-rpc-request-timeout.test.ts +137 -0
  119. package/port/oracle/pi-acp/test/unit/pi-rpc-session-path.test.ts +93 -0
  120. package/port/oracle/pi-acp/test/unit/pi-tools.test.ts +40 -0
  121. package/port/oracle/pi-acp/test/unit/prompt-to-pi-message.test.ts +74 -0
  122. package/port/oracle/pi-acp/test/unit/session-config-options.test.ts +217 -0
  123. package/port/oracle/pi-acp/test/unit/session-delete.test.ts +170 -0
  124. package/port/oracle/pi-acp/test/unit/session-restore.test.ts +239 -0
  125. package/port/oracle/pi-acp/test/unit/slash-commands.test.ts +36 -0
  126. package/port/oracle/pi-acp/test/unit/startup-info-env.test.ts +84 -0
  127. package/port/oracle/pi-acp/test/unit/startup-info-load-session.test.ts +53 -0
  128. package/port/oracle/pi-acp/test/unit/startup-info-project-packages.test.ts +74 -0
  129. package/port/oracle/pi-acp/test/unit/stdout-destroyed-does-not-crash.test.ts +32 -0
  130. package/port/oracle/pi-acp/test/unit/thinking-level-rpc.test.ts +43 -0
  131. package/port/schema/LICENSE +191 -0
  132. package/port/schema/schema.json +8015 -0
  133. package/port/upstream-tests.json +201 -0
  134. package/provenance.json +28 -0
@@ -0,0 +1,156 @@
1
+ package acp
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "os/exec"
7
+ "path/filepath"
8
+ "regexp"
9
+ "sort"
10
+ "strings"
11
+ "time"
12
+ )
13
+
14
+ var leadingV = regexp.MustCompile(`^[vV]`)
15
+
16
+ // pigVersionHeader is `pig --version`, or "" when the executable does not answer.
17
+ func pigVersionHeader(piCommand string) string {
18
+ if piCommand == "" {
19
+ piCommand = "pig"
20
+ }
21
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
22
+ defer cancel()
23
+ cmd := exec.CommandContext(ctx, piCommand, "--version")
24
+ out, err := cmd.Output()
25
+ text := jsTrim(string(out))
26
+ if text == "" && err != nil {
27
+ return ""
28
+ }
29
+ if text == "" {
30
+ if ee, ok := err.(*exec.ExitError); ok {
31
+ text = jsTrim(string(ee.Stderr))
32
+ }
33
+ }
34
+ return leadingV.ReplaceAllString(text, "")
35
+ }
36
+
37
+ func readDirNames(dir string) []string {
38
+ entries, err := os.ReadDir(dir)
39
+ if err != nil {
40
+ return nil
41
+ }
42
+ names := make([]string, 0, len(entries))
43
+ for _, e := range entries {
44
+ names = append(names, e.Name())
45
+ }
46
+ sort.Strings(names)
47
+ return names
48
+ }
49
+
50
+ func isDirPath(p string) (isDir, isFile, ok bool) {
51
+ st, err := os.Stat(p)
52
+ if err != nil {
53
+ return false, false, false
54
+ }
55
+ return st.IsDir(), st.Mode().IsRegular(), true
56
+ }
57
+
58
+ func skillsFromRoot(root string) []string {
59
+ var items []string
60
+ for _, e := range readDirNames(root) {
61
+ p := filepath.Join(root, e)
62
+ if _, isFile, ok := isDirPath(p); ok && isFile && strings.HasSuffix(strings.ToLower(e), ".md") {
63
+ items = append(items, p)
64
+ }
65
+ }
66
+ stack := []string{root}
67
+ for len(stack) > 0 {
68
+ dir := stack[len(stack)-1]
69
+ stack = stack[:len(stack)-1]
70
+ for _, name := range readDirNames(dir) {
71
+ if name == "node_modules" || name == ".git" {
72
+ continue
73
+ }
74
+ p := filepath.Join(dir, name)
75
+ isDir, isFile, ok := isDirPath(p)
76
+ if !ok {
77
+ continue
78
+ }
79
+ if isDir {
80
+ stack = append(stack, p)
81
+ } else if isFile && name == "SKILL.md" {
82
+ items = append(items, p)
83
+ }
84
+ }
85
+ }
86
+ return items
87
+ }
88
+
89
+ // BuildStartupInfo builds the markdown "startup info" block (the original's buildStartupInfo,
90
+ // without its npm update notice: see PORT.md, gap A18).
91
+ func BuildStartupInfo(cwd, piCommand string) string {
92
+ var md []string
93
+ if v := pigVersionHeader(piCommand); v != "" {
94
+ md = append(md, "pig v"+v, "---", "")
95
+ }
96
+ addSection := func(title string, items []string) {
97
+ var cleaned []string
98
+ for _, s := range items {
99
+ if s = jsTrim(s); s != "" {
100
+ cleaned = append(cleaned, s)
101
+ }
102
+ }
103
+ if len(cleaned) == 0 {
104
+ return
105
+ }
106
+ md = append(md, "## "+title)
107
+ for _, item := range cleaned {
108
+ md = append(md, "- "+item)
109
+ }
110
+ md = append(md, "")
111
+ }
112
+
113
+ var context []string
114
+ contextPath := filepath.Join(cwd, "AGENTS.md")
115
+ if _, err := os.Stat(contextPath); err == nil {
116
+ context = append(context, contextPath)
117
+ }
118
+ addSection("Context", context)
119
+
120
+ var skills []string
121
+ skills = append(skills, skillsFromRoot(filepath.Join(AgentDir(), "skills"))...)
122
+ skills = append(skills, skillsFromRoot(filepath.Join(userHome(), ".agents", "skills"))...)
123
+ skills = append(skills, skillsFromRoot(filepath.Join(cwd, ProjectDirName(), "skills"))...)
124
+ addSection("Skills", skills)
125
+
126
+ var prompts []string
127
+ for _, f := range readDirNames(filepath.Join(AgentDir(), "prompts")) {
128
+ if strings.HasSuffix(f, ".md") {
129
+ prompts = append(prompts, "/"+strings.TrimSuffix(f, ".md"))
130
+ }
131
+ }
132
+ addSection("Prompts", prompts)
133
+
134
+ var exts []string
135
+ extDir := filepath.Join(AgentDir(), "extensions")
136
+ for _, f := range readDirNames(extDir) {
137
+ if strings.HasSuffix(f, ".ts") || strings.HasSuffix(f, ".js") {
138
+ exts = append(exts, filepath.Join(extDir, f))
139
+ }
140
+ }
141
+ for _, sp := range []string{filepath.Join(AgentDir(), "settings.json"), filepath.Join(cwd, ProjectDirName(), "settings.json")} {
142
+ settings := readJSONObject(sp)
143
+ pkgs, _ := settings["packages"].([]any)
144
+ for _, p := range pkgs {
145
+ s := jsString(p)
146
+ if strings.HasPrefix(s, "npm:") {
147
+ exts = append(exts, s+"\n - index.ts")
148
+ } else {
149
+ exts = append(exts, s)
150
+ }
151
+ }
152
+ }
153
+ addSection("Extensions", exts)
154
+
155
+ return jsTrim(strings.Join(md, "\n")) + "\n"
156
+ }
@@ -0,0 +1,118 @@
1
+ package acp
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "sync"
8
+ "time"
9
+ )
10
+
11
+ // StoredSession is one entry of the adapter's session map.
12
+ type StoredSession struct {
13
+ SessionID string `json:"sessionId"`
14
+ Cwd string `json:"cwd"`
15
+ SessionFile string `json:"sessionFile"`
16
+ UpdatedAt string `json:"updatedAt"`
17
+ }
18
+
19
+ // Store maps ACP session ids to pi session files (SessionStore of the original).
20
+ type Store interface {
21
+ Get(sessionID string) *StoredSession
22
+ Upsert(entry StoredSession)
23
+ Delete(sessionID string)
24
+ }
25
+
26
+ type sessionMapFile struct {
27
+ Version int `json:"version"`
28
+ Sessions map[string]StoredSession `json:"sessions"`
29
+ }
30
+
31
+ // FileStore is a Store kept in one JSON file. Every operation reads the file again, so two
32
+ // adapter processes (two editor windows) see each other's entries.
33
+ type FileStore struct {
34
+ path string
35
+ mu sync.Mutex
36
+ }
37
+
38
+ // NewFileStore returns a store at path ("" = the default location under PIG_HOME).
39
+ func NewFileStore(path string) *FileStore {
40
+ if path == "" {
41
+ path = SessionMapPath()
42
+ }
43
+ return &FileStore{path: path}
44
+ }
45
+
46
+ func (s *FileStore) load() sessionMapFile {
47
+ empty := sessionMapFile{Version: 1, Sessions: map[string]StoredSession{}}
48
+ raw, err := os.ReadFile(s.path)
49
+ if err != nil {
50
+ return empty
51
+ }
52
+ var f struct {
53
+ Version int `json:"version"`
54
+ Sessions map[string]json.RawMessage `json:"sessions"`
55
+ }
56
+ if json.Unmarshal(raw, &f) != nil || f.Version != 1 || f.Sessions == nil {
57
+ return empty
58
+ }
59
+ for id, r := range f.Sessions {
60
+ var e StoredSession
61
+ if json.Unmarshal(r, &e) == nil {
62
+ empty.Sessions[id] = e
63
+ }
64
+ }
65
+ return empty
66
+ }
67
+
68
+ func (s *FileStore) save(f sessionMapFile) {
69
+ if err := os.MkdirAll(filepath.Dir(s.path), 0o755); err != nil {
70
+ return
71
+ }
72
+ b, err := json.MarshalIndent(f, "", " ")
73
+ if err != nil {
74
+ return
75
+ }
76
+ // Write to a sibling and rename, so a second adapter never reads half a file.
77
+ tmp := s.path + ".tmp"
78
+ if os.WriteFile(tmp, append(b, '\n'), 0o600) == nil {
79
+ if os.Rename(tmp, s.path) != nil {
80
+ _ = os.WriteFile(s.path, append(b, '\n'), 0o600)
81
+ _ = os.Remove(tmp)
82
+ }
83
+ }
84
+ }
85
+
86
+ // Get returns the entry for sessionID, or nil.
87
+ func (s *FileStore) Get(sessionID string) *StoredSession {
88
+ s.mu.Lock()
89
+ defer s.mu.Unlock()
90
+ if e, ok := s.load().Sessions[sessionID]; ok {
91
+ return &e
92
+ }
93
+ return nil
94
+ }
95
+
96
+ // Upsert records or refreshes an entry.
97
+ func (s *FileStore) Upsert(entry StoredSession) {
98
+ s.mu.Lock()
99
+ defer s.mu.Unlock()
100
+ f := s.load()
101
+ f.Sessions[entry.SessionID] = StoredSession{
102
+ SessionID: entry.SessionID, Cwd: entry.Cwd, SessionFile: entry.SessionFile,
103
+ UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
104
+ }
105
+ s.save(f)
106
+ }
107
+
108
+ // Delete removes an entry.
109
+ func (s *FileStore) Delete(sessionID string) {
110
+ s.mu.Lock()
111
+ defer s.mu.Unlock()
112
+ f := s.load()
113
+ if _, ok := f.Sessions[sessionID]; !ok {
114
+ return
115
+ }
116
+ delete(f.Sessions, sessionID)
117
+ s.save(f)
118
+ }